Compare commits

..
Author SHA1 Message Date
Robin Bygrave 6b05d2343b [maven-release-plugin] prepare release avaje-ebeanorm-7.8.1 2016-04-27 08:41:06 +12:00
Robin Bygrave fc748ea9c1 #667 - Remove deprecated @SqlSelect ... migrate to use RawSql / RawSqlBuilder 2016-04-27 08:40:00 +12:00
Robin Bygrave 7b8d2a2f9d #664 - Remove reference to @SqlInsert, @SqlUpdate, @SqlDelete from comment 2016-04-27 08:14:49 +12:00
Robin Bygrave f8b5a7f312 #666 - deleteAll(beans) return int , but deleteAll(beanType,ids) return void in EbeanServer 2016-04-27 08:04:43 +12:00
Robin Bygrave ae261d3782 #660 - findPagedList() does not handle raw sql order by 2016-04-26 22:54:34 +12:00
Robin Bygrave d2ae044d1f #665 - Remove deprecated @DocStoreEmbedded ... should migrate to @DocEmbedded 2016-04-26 21:35:57 +12:00
Robin Bygrave 03189ac92a #661 - ENH: Add deletePermanent(beanType, id) and deleteAllPermanent(beanType, ids) ... (basically missing API) 2016-04-26 21:30:52 +12:00
Robin Bygrave 2067568a5e No effective change - migrate test from deprecated method 2016-04-26 21:12:22 +12:00
Robin Bygrave 57417610a6 #663 - Change Postgres default Timestamp type to be "Timestamp with time zone" 2016-04-26 17:06:01 +12:00
Robin Bygrave 4cec878242 #662 - Postgres Timestamp with time zone testing 2016-04-26 16:41:11 +12:00
Robin Bygrave 2e7aec3b3d #662 - Postgres Timestamp with time zone testing - remove extra oracle test code 2016-04-26 10:13:33 +12:00
Robin Bygrave 104dc599e8 #662 - Postgres Timestamp with time zone testing 2016-04-26 10:12:37 +12:00
Robin Bygrave e769abf737 #659 - ENH: Add support for Instant with @WhenCreated @WhenModified 2016-04-22 16:51:30 +12:00
Robin Bygrave b79bb892f7 #636 - ENH: Add DB Migration runner (as alternative to running via FlywayDB etc) 2016-04-22 16:19:09 +12:00
Robin Bygrave 8f93f780fd Bump datasource dependency 2016-04-22 16:18:06 +12:00
Robin Bygrave 158a23a34d No effective change - reformat 2016-04-22 16:05:17 +12:00
Robin Bygrave 523ddbab63 #658 - REFACTOR: Simplify LocalDateTime -> Timestamp conversion, use Timestamp.valueOf() 2016-04-21 20:11:39 +12:00
Robin Bygrave 00d4298065 #656 - Remove remoteInsert(), remoteUpdate() remoteDelete() methods from BeanPersistListener 2016-04-21 12:33:06 +12:00
Robin Bygrave 4eb1aded2d #651 - ENH: Add type support for java.nio.file.Path ... such that it stores the underlying URI as VARCHAR/string 2016-04-21 11:34:33 +12:00
Robin Bygrave 221f5ad879 #651 - ENH: Add type support for java.nio.file.Path ... such that it stores the underlying URI as VARCHAR/string 2016-04-21 11:33:26 +12:00
Robin Bygrave 75e3b9276e #657 - POM - drop dependency on org.springframework.boot and avaje-ebeanorm-cluster 2016-04-21 10:38:26 +12:00
Robin Bygrave 29c51ecb95 #655 - DB Migration should not require running L2 cache (ie. Hazel cast server etc) 2016-04-21 10:33:09 +12:00
Robin Bygrave 8c535ba837 [maven-release-plugin] prepare for next development iteration 2016-04-18 21:01:11 +12:00
89 changed files with 1531 additions and 687 deletions
+17 -29
View File
@@ -3,13 +3,13 @@
<parent>
<groupId>org.avaje</groupId>
<artifactId>avaje-javaparent</artifactId>
<version>1.2</version>
<artifactId>oss-parent</artifactId>
<version>1.1</version>
</parent>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>7.7.1</version>
<version>7.8.1</version>
<packaging>jar</packaging>
<name>avaje-ebeanorm</name>
@@ -36,9 +36,8 @@
</developers>
<scm>
<connection>scm:git:https://github.com/ebean-orm/avaje-ebeanorm.git</connection>
<developerConnection>scm:git:https://github.com/ebean-orm/avaje-ebeanorm.git</developerConnection>
<url>https://github.com/ebean-orm/avaje-ebeanorm.git</url>
<tag>avaje-ebeanorm-7.8.1</tag>
</scm>
<dependencies>
@@ -64,14 +63,7 @@
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-datasource</artifactId>
<version>1.1.2</version>
</dependency>
<!-- bring in cluster support by default -->
<dependency>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-cluster</artifactId>
<version>1.1.1</version>
<version>1.1.3</version>
</dependency>
<dependency>
@@ -92,12 +84,11 @@
<version>[1.7.1,1.7.99)</version>
</dependency>
<!-- provided scope for Spring boot loader classpath scanning support -->
<!-- Jackson core used internally by Ebean -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>1.2.7.RELEASE</version>
<scope>provided</scope>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.6.5</version>
</dependency>
<!-- provided scope for JsonNode support -->
@@ -108,13 +99,6 @@
<scope>provided</scope>
</dependency>
<!-- Jackson core used internally by Ebean -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.6.5</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
@@ -152,6 +136,8 @@
<scope>provided</scope>
</dependency>
<!-- Test scope -->
<!--<dependency>-->
<!--<groupId>oracle</groupId>-->
<!--<artifactId>oracle-jdbc</artifactId>-->
@@ -228,9 +214,9 @@
</dependencies>
<!-- Enhance the meta beans -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
@@ -240,12 +226,12 @@
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-mavenenhancer</artifactId>
<version>4.9.2</version>
<executions>
<!-- Not going to enhance Model bean -->
<execution>
<id>test</id>
<phase>process-test-classes</phase>
@@ -282,7 +268,8 @@
</systemProperties>
</configuration>
</plugin>
<plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
@@ -292,6 +279,7 @@
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
@@ -317,7 +305,6 @@
<linksource>true</linksource>
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
@@ -327,6 +314,7 @@
</execution>
</executions>
</plugin>
</plugins>
</build>
+16 -2
View File
@@ -728,11 +728,25 @@ public final class Ebean {
return serverMgr.getDefaultServer().delete(beanType, id);
}
/**
* Delete permanent the bean given its type and id.
*/
public static int deletePermanent(Class<?> beanType, Object id) {
return serverMgr.getDefaultServer().deletePermanent(beanType, id);
}
/**
* Delete several beans given their type and id values.
*/
public static void deleteAll(Class<?> beanType, Collection<?> ids) {
serverMgr.getDefaultServer().deleteAll(beanType, ids);
public static int deleteAll(Class<?> beanType, Collection<?> ids) {
return serverMgr.getDefaultServer().deleteAll(beanType, ids);
}
/**
* Delete permanent several beans given their type and id values.
*/
public static int deleteAllPermanent(Class<?> beanType, Collection<?> ids) {
return serverMgr.getDefaultServer().deleteAllPermanent(beanType, ids);
}
/**
+23 -5
View File
@@ -1,6 +1,5 @@
package com.avaje.ebean;
import com.avaje.ebean.annotation.CacheStrategy;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.meta.MetaInfoManager;
@@ -1351,6 +1350,16 @@ public interface EbeanServer {
*/
int delete(Class<?> beanType, Object id, Transaction transaction);
/**
* Delete permanent given the bean type and id.
*/
int deletePermanent(Class<?> beanType, Object id);
/**
* Delete permanent given the bean type and id with an explicit transaction.
*/
int deletePermanent(Class<?> beanType, Object id, Transaction transaction);
/**
* Delete all the beans in the collection.
*/
@@ -1364,13 +1373,22 @@ public interface EbeanServer {
/**
* Delete several beans given their type and id values.
*/
void deleteAll(Class<?> beanType, Collection<?> ids);
int deleteAll(Class<?> beanType, Collection<?> ids);
/**
* Delete several beans given their type and id values with an explicit
* transaction.
* Delete several beans given their type and id values with an explicit transaction.
*/
void deleteAll(Class<?> beanType, Collection<?> ids, Transaction transaction);
int deleteAll(Class<?> beanType, Collection<?> ids, Transaction transaction);
/**
* Delete permanent for several beans given their type and id values.
*/
int deleteAllPermanent(Class<?> beanType, Collection<?> ids);
/**
* Delete permanent for several beans given their type and id values with an explicit transaction.
*/
int deleteAllPermanent(Class<?> beanType, Collection<?> ids, Transaction transaction);
/**
* Execute a Sql Update Delete or Insert statement. This returns the number of
+41 -12
View File
@@ -221,24 +221,39 @@ public final class OrderBy<T> implements Serializable {
private boolean ascending;
private String nulls;
private String highLow;
public Property(String property, boolean ascending) {
this.property = property;
this.ascending = ascending;
}
public Property(String property, boolean ascending, String nulls, String highLow) {
this.property = property;
this.ascending = ascending;
this.nulls = nulls;
this.highLow = highLow;
}
/**
* Return a copy of this Property with the path trimmed.
*/
public Property copyWithTrim(String path) {
return new Property(property.substring(path.length() + 1), ascending);
return new Property(property.substring(path.length() + 1), ascending, nulls, highLow);
}
@Override
public int hashCode() {
int hc = property.hashCode();
hc = hc * 31 + (ascending ? 0 : 1);
hc = hc * 31 + (nulls == null ? 0 : nulls.hashCode());
hc = hc * 31 + (highLow == null ? 0 : highLow.hashCode());
return hc;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
@@ -246,10 +261,11 @@ public final class OrderBy<T> implements Serializable {
if (!(obj instanceof Property)) {
return false;
}
Property e = (Property) obj;
return e.ascending == ascending
&& e.property.equals(property);
if (ascending != e.ascending) return false;
if (!property.equals(e.property)) return false;
if (nulls != null ? !nulls.equals(e.nulls) : e.nulls != null) return false;
return highLow != null ? highLow.equals(e.highLow) : e.highLow == null;
}
public String toString() {
@@ -257,10 +273,20 @@ public final class OrderBy<T> implements Serializable {
}
public String toStringFormat() {
if (ascending) {
return property;
if (nulls == null) {
if (ascending) {
return property;
} else {
return property + " desc";
}
} else {
return property + " desc";
StringBuilder sb = new StringBuilder();
sb.append(property);
if (!ascending) {
sb.append(" ").append("desc");
}
sb.append(" ").append(nulls).append(" ").append(highLow);
return sb.toString();
}
}
@@ -282,7 +308,7 @@ public final class OrderBy<T> implements Serializable {
* Return a copy of this property.
*/
public Property copy() {
return new Property(property, ascending);
return new Property(property, ascending, nulls, highLow);
}
/**
@@ -323,7 +349,6 @@ public final class OrderBy<T> implements Serializable {
String[] chunks = orderByClause.split(",");
for (int i = 0; i < chunks.length; i++) {
String[] pairs = chunks[i].split(" ");
Property p = parseProperty(pairs);
if (p != null) {
@@ -353,8 +378,12 @@ public final class OrderBy<T> implements Serializable {
boolean asc = isAscending(wordList.get(1));
return new Property(wordList.get(0), asc);
}
String m = "Expecting a max of 2 words in [" + Arrays.toString(pairs)
+ "] but got " + wordList.size();
if (wordList.size() == 4) {
// nulls high or nulls low as 3rd and 4th
boolean asc = isAscending(wordList.get(1));
return new Property(wordList.get(0), asc, wordList.get(2), wordList.get(3));
}
String m = "Expecting a 1, 2 or 4 words in [" + Arrays.toString(pairs) + "] but got " + wordList;
throw new RuntimeException(m);
}
@@ -1,21 +0,0 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Deprecated in favor of @DocEmbedded (i.e. renamed to @DocEmbedded)
*/
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Deprecated
public @interface DocStoreEmbedded {
/**
* The properties on the embedded bean to include in the index.
*/
String doc() default "";
}
@@ -6,19 +6,13 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify explicit sql for multiple select statements. Need to use this if you
* have more than one SqlSelect for a given bean.
* Mark and entity bean that is used solely via RawSql.
* <p>
* FUTURE: Support explicit sql for SqlInsert, SqlUpdate and SqlDelete.
* This means the entity bean has no base table specified (via @Table).
* </p>
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Sql {
/**
* The sql select statements.
*/
SqlSelect[] select() default { @SqlSelect };
}
@@ -1,298 +0,0 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.avaje.ebean.Query;
/**
* Specify an explicit sql select statement to use for querying an entity bean.
* <p>
* The reason for using explicit sql is that you want better control over the
* exact sql or sql that Ebean does not generate for you (such as group by,
* union, intersection, window functions, recursive queries).
* </p>
* <p>
* An example of two sql select queries deployed on the ReportTopic entity bean.
* The first one has no name specified so it becomes the default query. The
* second query extends the first adding a where clause with a named parameter.
* </p>
*
* <pre class="code">
* ...
* &#064;Entity
* &#064;Sql(select = {
* &#064;SqlSelect(query =
* &quot;select t.id, t.title, count(p.id) as score &quot;+
* &quot;from f_topic t &quot;+
* &quot;join f_topic_post p on p.topic_id = t.id &quot;+
* &quot;group by t.id, t.title&quot;),
* &#064;SqlSelect(
* name = &quot;with.title&quot;,
* extend = &quot;default&quot;,
* debug = true,
* where = &quot;title like :likeTitle&quot;)
* })
* public class ReportTopic
* &#064;Id Integer id;
* String title;
* Double score;
* ...
* </pre>
*
* <p>
* An example using the first "default" query.
* </p>
*
* <pre class="code">
*
* List&lt;ReportTopic&gt; list =
* Ebean.find(ReportTopic.class)
* .having().gt(&quot;score&quot;, 0)
* .findList();
*
* </pre>
*
* <p>
* The resulting sql, note the having clause has been added.
* </p>
*
* <pre class="code">
* select t.id, t.title, count(p.id) as score
* from f_topic t join f_topic_post p on p.topic_id = t.id
* group by t.id, t.title
* having count(p.id) &gt; ?
* </pre>
*
* <p>
* An example using the second query. Note the named parameter "likeTitle" must
* be set.
* </p>
*
* <pre class="code">
* List&lt;ReportTopic&gt; list =
* Ebean.find(ReportTopic.class, &quot;with.title&quot;)
* .set(&quot;likeTitle&quot;, &quot;a%&quot;)
* .findList();
* </pre>
*
* <p>
* Ebean tries to parse the sql in the query to determine 4 things
* <li>Location for inserting WHERE expressions (if required)</li>
* <li>Location for inserting HAVING expressions (if required)</li>
* <li>Mapping of columns to bean properties</li>
* <li>The order by clause</li>
* </p>
* <p>
* If Ebean is unable to parse out this information (perhaps because the sql
* contains multiple select from keywords etc) then you need to manually specify
* it.
* </p>
* <p>
* Insert ${where} or ${andWhere} into the location where Ebean can insert any
* expressions added to the where clause. Use ${andWhere} if the sql already has
* the WHERE keyword and Ebean will instead start with a AND keyword.
* </p>
* <p>
* Insert ${having} or ${andHaving} into the location where Ebean can insert any
* expressions added to the having clause. Use ${andHaving} if the sql already
* has a HAVING keyword and Ebean will instead start with a AND keyword.
* </p>
* <p>
* Use the columnMapping property if Ebean is unable to determine the columns
* and map them to bean properties.
* </p>
* <p>
* Example with ${andWhere} & ${having}.
* </p>
*
* <pre class="code">
* &#064;SqlSelect(
* name = &quot;explicit.where&quot;,
* query =
* &quot;select t.id, t.title, count(p.id) as score &quot;+
* &quot;from f_topic t, f_topic_post p &quot;+
* &quot;where p.topic_id = t.id ${andWhere} &quot;+
* &quot;group by t.id, t.title ${having}&quot;),
* </pre>
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Deprecated
public @interface SqlSelect {
/**
* The name of the query. If left blank this is assumed to be the default
* query for this bean type.
* <p>
* This will default to "default" and in that case becomes the default query
* used for the bean.
* </p>
*/
String name() default "default";
/**
* The tableAlias used when adding where expressions to the query.
*/
String tableAlias() default "";
/**
* The sql select statement.
* <p>
* If this query <em>extends</em> another then this string is appended to the
* parent query string. Often when using <em>extend</em> you will leave the
* query part blank and just specify a where and/or having clauses.
* </p>
* <p>
* This sql <em>CAN NOT</em> contain named parameters. You have to put these
* in the separate where and/or having sections.
* </p>
* <p>
* Ebean automatically tries to determine the location in the sql string for
* putting in additional where or having clauses. If Ebean is unable to
* successfully determine this then you have to explicitly specify these
* locations by including
* <em>${where} or ${andWhere} and ${having} or ${andHaving}</em> in the sql.
* </p>
* <p>
* <b>${where}</b> location of where clause (and will add WHERE ... ) <br/>
* Use this when there is no where clause in the sql. If expressions are added
* to the where clause Ebean will put them in at this location starting with
* the WHERE keyword.
* <p>
* <p>
* <b>${andWhere}</b> <br/>
* Use this instead of ${where} if there IS an existing where clause in the
* sql. Ebean will add the expressions starting with the AND keyword.
* <p>
* <b>${having}</b> location of having clause (and will add HAVING... ) <br/>
* </p>
* <p>
* <b>${andHaving}</b> <br/>
* Use this instead of ${having} when there IS an existing HAVING clause.
* Ebean will add the expressions starting with the AND keyword.
* </p>
* <p>
* You can include one of ${where} OR ${andWhere} but not both.
* </p>
* <p>
* You can include one of ${having} OR ${andHaving} but not both.
* </p>
*/
String query() default "";
/**
* Specify the name of a sql-select query that this one 'extends'.
* <p>
* When a query is extended the sql query contents are appended together. The
* where and having clauses are NOT appended but overridden.
* </p>
*/
String extend() default "";
/**
* Specify a where clause typically containing named parameters.
* <p>
* If a where clause is specified with named parameters then they will need to
* be set on the query via {@link Query#setParameter(String, Object)}.
* </p>
* <p>
* In the example below the query specifies a where clause that includes a
* named parameter "likeTitle".
* </p>
*
* <pre class="code">
* ...
* &#064;Entity
* &#064;Sql(select = {
* ...
* &#064;SqlSelect(
* name = &quot;with.title&quot;,
* extend = &quot;default&quot;,
* debug = true,
* where = &quot;title like :likeTitle&quot;)
* })
* public class ReportTopic
* ...
* </pre>
*
* <p>
* Example use of the above named query.
* </p>
*
* <pre class="code">
*
* Query&lt;ReportTopic&gt; query0 = Ebean.createQuery(ReportTopic.class, &quot;with.title&quot;);
*
* query0.set(&quot;likeTitle&quot;, &quot;Bana%&quot;);
*
* List&lt;ReportTopic&gt; list0 = query0.findList();
* </pre>
*
*/
String where() default "";
/**
* Specify a having clause typically containing named parameters.
* <p>
* If a having clause is specified with named parameters then they will need
* to be set on the query via {@link Query#setParameter(String, Object)}.
* </p>
*/
String having() default "";
/**
* (Optional) Explicitly specify column to property mapping.
* <p>
* This is required when Ebean is unable to parse the sql. This could occur if
* the sql contains multiple select keywords etc.
* </p>
* <p>
* Specify the columns and property names they map to in the format.
* </p>
*
* <pre class="code">
* column1 propertyName1, column2 propertyName2, ...
* </pre>
*
* <p>
* Optionally put a AS keyword between the column and property.
* </p>
*
* <pre class="code">
* // the AS keyword is optional
* column1 AS propertyName1, column2 propertyName2, ...
* </pre>
*
* <p>
* <b>column</b> should contain the table alias if there is one
* </p>
* <p>
* <b>propertyName</b> should match the property name.
* </p>
*
* <p>
* Example mapping 5 columns to properties.
* </p>
*
* <pre class="code">
* columnMapping=&quot;t.id, t.bug_body description, t.bug_title as title, count(p.id) as scoreValue&quot;,
* </pre>
*
* <p>
* Without this set Ebean will parse the sql looking for the select clause and
* try to map the columns to property names. It is expected that Ebean will
* not be able to successfully parse some sql and for those cases you should
* specify the column to property mapping explicitly.
* </p>
*
*/
String columnMapping() default "";
/**
* Set this to true to have debug output when Ebean parses the sql-select.
*/
boolean debug() default false;
}
@@ -33,6 +33,13 @@ public class ClassLoadConfig {
return isPresent("java.time.LocalDate");
}
/**
* Return true if Java7 is present.
*/
public boolean isJava7Present() {
return isPresent("java.nio.file.Path");
}
/**
* Return true if the Joda types are available and should be supported.
*/
@@ -366,8 +366,8 @@ public class DbMigrationConfig {
metaTable = properties.get("migration.metaTable", metaTable);
runPlaceholders = properties.get("migration.placeholders", runPlaceholders);
String adminUser = properties.get("datasource."+serverName+".user", dbUser);
adminUser = properties.get("datasource."+serverName+".adminuser", adminUser);
String adminUser = properties.get("datasource."+serverName+".username", dbUser);
adminUser = properties.get("datasource."+serverName+".adminusername", adminUser);
dbUser = properties.get("migration.dbuser", adminUser);
String adminPwd = properties.get("datasource."+serverName+".password", dbPassword);
@@ -224,6 +224,11 @@ public class ServerConfig {
private boolean eagerFetchLobs;
/**
* Timezone used to get/set Timestamp values via JDBC.
*/
private String dataTimeZone;
private boolean ddlGenerate;
private boolean ddlRun;
@@ -841,6 +846,23 @@ public class ServerConfig {
this.migrationConfig = migrationConfig;
}
/**
* Return the time zone to use when reading/writing Timestamps via JDBC.
* <p>
* When set a Calendar object is used in JDBC calls when reading/writing Timestamp objects.
* </p>
*/
public String getDataTimeZone() {
return System.getProperty("ebean.dataTimeZone", dataTimeZone);
}
/**
* Set the time zone to use when reading/writing Timestamps via JDBC.
*/
public void setDataTimeZone(String dataTimeZone) {
this.dataTimeZone = dataTimeZone;
}
/**
* Return the suffix appended to the base table to derive the view that contains the union
* of the base table and the history table in order to support asOf queries.
@@ -2369,6 +2391,7 @@ public class ServerConfig {
changeLogIncludeInserts = p.getBoolean("changeLogIncludeInserts", changeLogIncludeInserts);
expressionEqualsWithNullAsNoop = p.getBoolean("expressionEqualsWithNullAsNoop", expressionEqualsWithNullAsNoop);
dataTimeZone = p.get("dataTimeZone", dataTimeZone);
asOfViewSuffix = p.get("asOfViewSuffix", asOfViewSuffix);
asOfSysPeriod = p.get("asOfSysPeriod", asOfSysPeriod);
historyTableSuffix = p.get("historyTableSuffix", historyTableSuffix);
@@ -14,6 +14,7 @@ import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Properties;
/**
* Database platform specific settings.
@@ -175,6 +176,10 @@ public class DatabasePlatform {
public DatabasePlatform() {
}
public void configure(Properties properties) {
// by default do nothing
}
/**
* Return the name of the DatabasePlatform.
* <p>
@@ -7,6 +7,7 @@ import com.avaje.ebean.dbmigration.ddlgeneration.platform.PostgresDdl;
import javax.sql.DataSource;
import java.sql.Types;
import java.util.Properties;
/**
* Postgres v9 specific platform.
@@ -50,6 +51,7 @@ public class PostgresPlatform extends DatabasePlatform {
dbTypeMap.put(Types.DOUBLE, new DbType("float"));
dbTypeMap.put(Types.TINYINT, new DbType("smallint"));
dbTypeMap.put(Types.DECIMAL, new DbType("decimal", 38));
dbTypeMap.put(Types.TIMESTAMP, new DbType("timestamptz"));
dbTypeMap.put(Types.BINARY, dbBytea);
dbTypeMap.put(Types.VARBINARY, dbBytea);
@@ -58,7 +60,16 @@ public class PostgresPlatform extends DatabasePlatform {
dbTypeMap.put(Types.CLOB, dbTypeText);
dbTypeMap.put(Types.LONGVARBINARY, dbBytea);
dbTypeMap.put(Types.LONGVARCHAR, dbTypeText);
}
@Override
public void configure(Properties properties) {
super.configure(properties);
String tsType = properties.getProperty("ebean.postgres.timestamp");
if (tsType != null) {
// set timestamp type to "timestamp" without time zone
dbTypeMap.put(Types.TIMESTAMP, new DbType(tsType));
}
}
/**
@@ -21,7 +21,7 @@ import java.util.List;
*/
public class MigrationRunner {
private static final Logger logger = LoggerFactory.getLogger(MigrationRunner.class);
public static final Logger logger = LoggerFactory.getLogger("org.avaje.ebean.DbMigration");
private final EbeanServer server;
@@ -48,8 +48,16 @@ public class MigrationRunner {
String migrationUser = migrationConfig.getDbUser();
String migrationPwd = migrationConfig.getDbPassword();
if (migrationUser == null) {
throw new IllegalStateException("No DB migration user specified (to run the db migration) ?");
}
DataSource dataSource = server.getPluginApi().getDataSource();
if (dataSource == null) {
throw new IllegalStateException("No dataSource when trying to run migration? "
+"Maybe trying to generate DBMigration when ebean.migration.run=true is set? "
+"Perhaps need to set ebean.migration.run=false in test-ebean.properties?");
}
Connection connection;
try {
@@ -59,8 +67,8 @@ public class MigrationRunner {
}
try {
logger.debug("using db user [{}] to run migrations ...");
connection.setAutoCommit(false);
runMigrations(resources, connection);
connection.commit();
@@ -85,6 +93,8 @@ public class MigrationRunner {
// get the migrations in version order
List<LocalMigrationResource> localVersions = resources.getVersions();
logger.info("local migrations:{} existing migrations:{}", localVersions.size(), table.size());
LocalMigrationResource priorVersion = null;
// run migrations in order
@@ -45,7 +45,7 @@ class MigrationMetaRow {
MigrationMetaRow(SqlRow row) {
id = row.getInteger("id");
status = row.getString("status");
runVersion = row.getString("row_version");
runVersion = row.getString("run_version");
depVersion = row.getString("dep_version");
comment = row.getString("comment");
checksum = row.getInteger("checksum");
@@ -53,6 +53,10 @@ class MigrationMetaRow {
runBy = row.getString("run_by");
}
public String toString() {
return "id:" + id + " status:" + status + " runVersion:" + runVersion + " comment:" + comment + " runOn:" + runOn + " runBy:" + runBy;
}
/**
* Return the id for this migration.
*/
@@ -7,11 +7,11 @@ import com.avaje.ebean.SqlUpdate;
import com.avaje.ebean.config.DbMigrationConfig;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.dbmigration.MigrationRunner;
import com.avaje.ebean.plugin.SpiServer;
import com.avaje.ebeaninternal.server.transaction.ExternalJdbcTransaction;
import com.avaje.ebeaninternal.util.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URL;
@@ -27,7 +27,7 @@ import java.util.Map;
*/
public class MigrationTable {
private static final Logger logger = LoggerFactory.getLogger(MigrationTable.class);
private static final Logger logger = MigrationRunner.logger;
private final Connection connection;
@@ -70,6 +70,13 @@ public class MigrationTable {
this.envUserName = System.getProperty("user.name");
}
/**
* Return the number of migrations in the DB migration table.
*/
public int size() {
return migrations.size();
}
/**
* Create the ScriptTransform for placeholder key/value replacement.
*/
@@ -229,6 +236,10 @@ public class MigrationTable {
*/
private void addMigration(MigrationMetaRow metaRow) {
lastMigration = metaRow;
migrations.put(metaRow.getRunVersion(), metaRow);
String runVersion = metaRow.getRunVersion();
if (runVersion == null) {
throw new IllegalStateException("No runVersion in db migration table row? " + metaRow);
}
migrations.put(runVersion, metaRow);
}
}
@@ -46,33 +46,4 @@ public abstract class AbstractBeanPersistListener implements BeanPersistListener
return false;
}
/**
* Notify that a bean was inserted on another node of the cluster.
*
* @param id the id value of the inserted bean
*/
@Override
public void remoteInsert(Object id) {
// do nothing
}
/**
* Notify that a bean was updated on another node of the cluster.
*
* @param id the id value of the updated bean.
*/
@Override
public void remoteUpdate(Object id) {
// do nothing
}
/**
* Notify that a bean was deleted on another node of the cluster.
*
* @param id the id value of the deleted bean.
*/
@Override
public void remoteDelete(Object id) {
// do nothing
}
}
@@ -74,28 +74,4 @@ public interface BeanPersistListener {
*/
boolean deleted(Object bean);
/**
* Notify that a bean was inserted on another node of the cluster.
*
* @param id
* the id value of the inserted bean
*/
void remoteInsert(Object id);
/**
* Notify that a bean was updated on another node of the cluster.
*
* @param id
* the id value of the updated bean.
*/
void remoteUpdate(Object id);
/**
* Notify that a bean was deleted on another node of the cluster.
*
* @param id
* the id value of the deleted bean.
*/
void remoteDelete(Object id);
}
@@ -16,6 +16,7 @@ import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.query.CQuery;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone;
import java.util.List;
@@ -187,4 +188,9 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
* user context information (user id, user ip address etc).
*/
ReadAuditPrepare getReadAuditPrepare();
/**
* Return the DataTimeZone to use when reading/writing timestamps via JDBC.
*/
DataTimeZone getDataTimeZone();
}
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.core;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -12,35 +13,35 @@ import org.slf4j.LoggerFactory;
public abstract class BeanRequest {
private static final Logger log = LoggerFactory.getLogger(BeanRequest.class);
/**
* The server processing the request.
*/
protected final SpiEbeanServer ebeanServer;
/**
* The transaction this is part of.
*/
protected SpiTransaction transaction;
/**
* The server processing the request.
*/
protected final SpiEbeanServer ebeanServer;
protected boolean createdTransaction;
/**
* The transaction this is part of.
*/
protected SpiTransaction transaction;
public BeanRequest(SpiEbeanServer ebeanServer, SpiTransaction t) {
this.ebeanServer = ebeanServer;
this.transaction = t;
}
protected boolean createdTransaction;
/**
* A helper method for creating an implicit transaction is it is required.
* <p>
* A transaction may have been passed in or active in the thread local. If
* not then create one implicitly to handle the request.
* </p>
public BeanRequest(SpiEbeanServer ebeanServer, SpiTransaction t) {
this.ebeanServer = ebeanServer;
this.transaction = t;
}
/**
* A helper method for creating an implicit transaction is it is required.
* <p>
* A transaction may have been passed in or active in the thread local. If
* not then create one implicitly to handle the request.
* </p>
*
* @return True if a transaction was set (from current or created).
*/
public boolean createImplicitTransIfRequired() {
if (transaction != null) {
*/
public boolean createImplicitTransIfRequired() {
if (transaction != null) {
return false;
}
transaction = ebeanServer.getCurrentServerTransaction();
@@ -50,7 +51,7 @@ public abstract class BeanRequest {
createdTransaction = true;
}
return true;
}
}
/**
* Commit this transaction if it was created for this request.
@@ -61,40 +62,40 @@ public abstract class BeanRequest {
}
}
/**
* Rollback the transaction if it was created for this request.
*/
public void rollbackTransIfRequired() {
if (createdTransaction) {
try {
transaction.rollback();
} catch (Exception e) {
// Just log this and carry on. A previous exception has been
// thrown and if this rollback throws exception it likely means
// that the connection is broken (and the datasource and db will cleanup)
log.error("Error trying to rollback a transaction (after a prior exception thrown)", e);
}
}
}
/**
* Rollback the transaction if it was created for this request.
*/
public void rollbackTransIfRequired() {
if (createdTransaction) {
try {
transaction.rollback();
} catch (Exception e) {
// Just log this and carry on. A previous exception has been
// thrown and if this rollback throws exception it likely means
// that the connection is broken (and the datasource and db will cleanup)
log.error("Error trying to rollback a transaction (after a prior exception thrown)", e);
}
}
}
/**
* Return the server processing the request. Made available for
* BeanController and BeanFinder.
*/
public EbeanServer getEbeanServer() {
return ebeanServer;
}
/**
* Return the server processing the request. Made available for
* BeanController and BeanFinder.
*/
public EbeanServer getEbeanServer() {
return ebeanServer;
}
public SpiEbeanServer getServer() {
return ebeanServer;
}
/**
* Return the Transaction associated with this request.
*/
public SpiTransaction getTransaction() {
return transaction;
}
/**
* Return the Transaction associated with this request.
*/
public SpiTransaction getTransaction() {
return transaction;
}
/**
* Return true if SQL should be logged for this transaction.
@@ -109,4 +110,11 @@ public abstract class BeanRequest {
public boolean isLogSummary() {
return transaction.isLogSummary();
}
/**
* Return the DataTimeZone to use.
*/
public DataTimeZone getDataTimeZone() {
return ebeanServer.getDataTimeZone();
}
}
@@ -108,7 +108,7 @@ public class DefaultContainer implements SpiContainer {
// inform the NamingConvention of the associated DatabasePlatform
serverConfig.getNamingConvention().setDatabasePlatform(serverConfig.getDatabasePlatform());
ServerCacheManager cacheManager = getCacheManager(serverConfig);
ServerCacheManager cacheManager = getCacheManager(online, serverConfig);
SpiBackgroundExecutor bgExecutor = createBackgroundExecutor(serverConfig);
@@ -147,9 +147,10 @@ public class DefaultContainer implements SpiContainer {
/**
* Create and return the CacheManager.
*/
private ServerCacheManager getCacheManager(ServerConfig serverConfig) {
private ServerCacheManager getCacheManager(boolean online, ServerConfig serverConfig) {
if (serverConfig.isDisableL2Cache()) {
if (!online || serverConfig.isDisableL2Cache()) {
// use local only L2 cache implementation as placeholder
return new DefaultServerCacheManager();
}
@@ -240,6 +241,7 @@ public class DefaultContainer implements SpiContainer {
if (dbPlatform == null) {
DatabasePlatformFactory factory = new DatabasePlatformFactory();
DatabasePlatform db = factory.create(config);
db.configure(config.getProperties());
config.setDatabasePlatform(db);
logger.info("DatabasePlatform name:" + config.getName() + " platform:" + db.getName());
}
@@ -64,6 +64,7 @@ import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
import com.avaje.ebeaninternal.server.transaction.TransactionManager;
import com.avaje.ebeaninternal.server.transaction.TransactionScopeManager;
import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone;
import com.avaje.ebeaninternal.util.ParamTypeHelper;
import com.avaje.ebeaninternal.util.ParamTypeHelper.TypeInfo;
import com.avaje.ebeanservice.docstore.api.DocStoreIntegration;
@@ -108,6 +109,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final TransactionScopeManager transactionScopeManager;
private final DataTimeZone dataTimeZone;
private final CallStackFactory callStackFactory = new DefaultCallStackFactory();
private final int maxCallStack;
@@ -228,6 +231,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.beanLoader = new DefaultBeanLoader(this);
this.jsonContext = config.createJsonContext(this);
this.dataTimeZone = config.getDataTimeZone();
DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this);
this.transactionManager = config.createTransactionManager(docStoreComponents.updateProcessor());
@@ -284,7 +288,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
public DatabasePlatform getDatabasePlatform() {
return databasePlatform;
}
@Override
public DataTimeZone getDataTimeZone() {
return dataTimeZone;
}
@Override
public MetaInfoManager getMetaInfoManager() {
return metaInfoManager;
@@ -1756,11 +1765,23 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
public int delete(Class<?> beanType, Object id, Transaction t) {
return delete(beanType, id, t, false);
}
public int deletePermanent(Class<?> beanType, Object id) {
return delete(beanType, id, null, true);
}
public int deletePermanent(Class<?> beanType, Object id, Transaction t) {
return delete(beanType, id, t, true);
}
private int delete(Class<?> beanType, Object id, Transaction t, boolean permanent) {
TransWrapper wrap = initTransIfRequired(t);
try {
SpiTransaction trans = wrap.transaction;
int rowCount = persister.delete(beanType, id, trans);
int rowCount = persister.delete(beanType, id, trans, permanent);
wrap.commitIfCreated();
return rowCount;
@@ -1772,18 +1793,33 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
@Override
public void deleteAll(Class<?> beanType, Collection<?> ids) {
deleteAll(beanType, ids, null);
public int deleteAll(Class<?> beanType, Collection<?> ids) {
return deleteAll(beanType, ids, null);
}
@Override
public void deleteAll(Class<?> beanType, Collection<?> ids, Transaction t) {
public int deleteAll(Class<?> beanType, Collection<?> ids, Transaction t) {
return deleteAll(beanType, ids, t, false);
}
@Override
public int deleteAllPermanent(Class<?> beanType, Collection<?> ids) {
return deleteAll(beanType, ids, null, true);
}
@Override
public int deleteAllPermanent(Class<?> beanType, Collection<?> ids, Transaction t) {
return deleteAll(beanType, ids, t, true);
}
private int deleteAll(Class<?> beanType, Collection<?> ids, Transaction t, boolean permanent) {
TransWrapper wrap = initTransIfRequired(t);
try {
SpiTransaction trans = wrap.transaction;
persister.deleteMany(beanType, ids, trans);
int count = persister.deleteMany(beanType, ids, trans, permanent);
wrap.commitIfCreated();
return count;
} catch (RuntimeException e) {
wrap.rollbackIfCreated();
@@ -22,6 +22,9 @@ import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogListener;
import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogPrepare;
import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogRegister;
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
import com.avaje.ebeaninternal.server.core.timezone.CloneDataTimeZone;
import com.avaje.ebeaninternal.server.core.timezone.NoDataTimeZone;
import com.avaje.ebeaninternal.server.core.timezone.SimpleDataTimeZone;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
import com.avaje.ebeaninternal.server.deploy.DeployOrmXml;
import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory;
@@ -29,6 +32,7 @@ import com.avaje.ebeaninternal.server.deploy.parse.DeployCreateProperties;
import com.avaje.ebeaninternal.server.deploy.parse.DeployInherit;
import com.avaje.ebeaninternal.server.deploy.parse.DeployUtil;
import com.avaje.ebeaninternal.server.expression.DefaultExpressionFactory;
import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone;
import org.avaje.datasource.DataSourcePool;
import com.avaje.ebeaninternal.server.persist.Binder;
import com.avaje.ebeaninternal.server.persist.DefaultPersister;
@@ -79,6 +83,8 @@ public class InternalConfiguration {
private final TypeManager typeManager;
private final DataTimeZone dataTimeZone;
private final Binder binder;
private final DeployCreateProperties deployCreateProperties;
@@ -136,7 +142,8 @@ public class InternalConfiguration {
DatabasePlatform databasePlatform = serverConfig.getDatabasePlatform();
this.binder = getBinder(typeManager, databasePlatform);
this.dataTimeZone = initDataTimeZone();
this.binder = getBinder(typeManager, databasePlatform, dataTimeZone);
this.cQueryEngine = new CQueryEngine(databasePlatform, binder, asOfTableMapping, serverConfig.getAsOfSysPeriod(), draftTableMap);
}
@@ -218,15 +225,15 @@ public class InternalConfiguration {
/**
* For 'As Of' queries return the number of bind variables per predicate.
*/
private Binder getBinder(TypeManager typeManager, DatabasePlatform databasePlatform) {
private Binder getBinder(TypeManager typeManager, DatabasePlatform databasePlatform, DataTimeZone dataTimeZone) {
JsonExpressionHandler jsonHandler = getJsonExpressionHandler(databasePlatform);
DbHistorySupport historySupport = databasePlatform.getHistorySupport();
if (historySupport == null) {
return new Binder(typeManager, 0, false, jsonHandler);
return new Binder(typeManager, 0, false, jsonHandler, dataTimeZone);
}
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isBindWithFromClause(), jsonHandler);
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isBindWithFromClause(), jsonHandler, dataTimeZone);
}
/**
@@ -381,4 +388,24 @@ public class InternalConfiguration {
return new DefaultTransactionScopeManager(transactionManager);
}
}
/**
* Create the DataTimeZone implementation to use.
*/
private DataTimeZone initDataTimeZone() {
String tz = serverConfig.getDataTimeZone();
if (tz == null) {
return new NoDataTimeZone();
}
if (getDatabasePlatform().getName().toLowerCase().startsWith("oracle")) {
return new CloneDataTimeZone(tz);
} else {
return new SimpleDataTimeZone(tz);
}
}
public DataTimeZone getDataTimeZone() {
return dataTimeZone;
}
}
@@ -304,7 +304,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
/**
* Set the cache notify status.
*/
public void setNotifyCache() {
private void setNotifyCache() {
this.notifyCache = beanDescriptor.isCacheNotify(publish);
}
@@ -327,7 +327,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
/**
* Notify/Update the local L2 cache after the transaction has successfully committed.
* Collect L2 cache changes to be applied after the transaction has successfully committed.
*/
public void notifyCache(CacheChangeSet changeSet) {
if (notifyCache) {
@@ -340,7 +340,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
break;
case DELETE:
case SOFT_DELETE:
// Bean deleted from cache early via postDelete()
beanDescriptor.cacheHandleDelete(idValue, this, changeSet);
break;
default:
@@ -949,7 +948,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Takes into account transaction setting and JDBC batch.
* </p>
*/
public boolean determineUpdateAllLoadedProperties() {
private boolean determineUpdateAllLoadedProperties() {
Boolean txnUpdateAll = transaction.isUpdateAllLoadedProperties();
if (txnUpdateAll != null) {
@@ -61,7 +61,7 @@ public interface Persister {
* This will also cascade delete one level of children.
* </p>
*/
int delete(Class<?> beanType, Object id, Transaction transaction);
int delete(Class<?> beanType, Object id, Transaction transaction, boolean permanent);
/**
* Delete the bean.
@@ -71,7 +71,7 @@ public interface Persister {
/**
* Delete multiple beans given a collection of Id values.
*/
void deleteMany(Class<?> beanType, Collection<?> ids, Transaction transaction);
int deleteMany(Class<?> beanType, Collection<?> ids, Transaction transaction, boolean permanent);
/**
* Execute the Update.
@@ -0,0 +1,22 @@
package com.avaje.ebeaninternal.server.core.timezone;
import java.util.Calendar;
/**
* Implementation of DataTimeZone that clones the Calendar instance.
* <p>
* Used with Oracle JDBC driver as that wants to mutate the Calender.
* </p>
*/
public class CloneDataTimeZone extends SimpleDataTimeZone {
public CloneDataTimeZone(String zoneId) {
super(zoneId);
}
@Override
public Calendar getTimeZone() {
// return cloned copy for Oracle to muck around with
return (Calendar)zone.clone();
}
}
@@ -0,0 +1,14 @@
package com.avaje.ebeaninternal.server.core.timezone;
import java.util.Calendar;
/**
* Define if a Calendar representing the time zone should be used in JDBC calls.
*/
public interface DataTimeZone {
/**
* Return the Calendar to use for Timezone information.
*/
Calendar getTimeZone();
}
@@ -0,0 +1,15 @@
package com.avaje.ebeaninternal.server.core.timezone;
import java.util.Calendar;
/**
* Implementation of DataTimeZone when no time zone is specified.
*/
public class NoDataTimeZone implements DataTimeZone {
@Override
public Calendar getTimeZone() {
// return null so Calendar is not used
return null;
}
}
@@ -0,0 +1,22 @@
package com.avaje.ebeaninternal.server.core.timezone;
import java.util.Calendar;
import java.util.TimeZone;
/**
* Implementation of DataTimeZone when single Calendar instance is used.
*/
public class SimpleDataTimeZone implements DataTimeZone {
protected final Calendar zone;
public SimpleDataTimeZone(String zoneId) {
this.zone = Calendar.getInstance(TimeZone.getTimeZone(zoneId));
}
@Override
public Calendar getTimeZone() {
// return null so Calendar is not used
return zone;
}
}
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.deploy;
import com.avaje.ebean.OrderBy;
import com.avaje.ebean.PersistenceContextScope;
import com.avaje.ebean.Query;
import com.avaje.ebean.RawSql;
import com.avaje.ebean.SqlUpdate;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.ValuePair;
@@ -2719,6 +2720,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
if (idProperty != null && !idProperty.isEmbedded()) {
OrderBy<T> orderBy = query.getOrderBy();
if (orderBy == null || orderBy.isEmpty()) {
RawSql rawSql = query.getRawSql();
if (rawSql != null) {
query.order(rawSql.getSql().getOrderBy());
}
query.order().asc(idProperty.getName());
} else if (!orderBy.containsProperty(idProperty.getName())){
query.order().asc(idProperty.getName());
@@ -83,7 +83,6 @@ public class ChainedBeanPersistListener implements BeanPersistListener {
}
}
public boolean deleted(Object bean) {
boolean notifyCluster = false;
for (int i = 0; i < chain.length; i++) {
@@ -104,24 +103,6 @@ public class ChainedBeanPersistListener implements BeanPersistListener {
return notifyCluster;
}
public void remoteDelete(Object id) {
for (int i = 0; i < chain.length; i++) {
chain[i].remoteDelete(id);
}
}
public void remoteInsert(Object id) {
for (int i = 0; i < chain.length; i++) {
chain[i].remoteInsert(id);
}
}
public void remoteUpdate(Object id) {
for (int i = 0; i < chain.length; i++) {
chain[i].remoteUpdate(id);
}
}
public boolean updated(Object bean, Set<String> updatedProperties) {
boolean notifyCluster = false;
for (int i = 0; i < chain.length; i++) {
@@ -1,7 +1,5 @@
package com.avaje.ebeaninternal.server.deploy;
import com.avaje.ebean.annotation.SqlSelect;
/**
* Meta data for a sql-select object.
* <p>
@@ -19,18 +17,6 @@ public class DRawSqlMeta {
private String having;
private String columnMapping;
public DRawSqlMeta(SqlSelect sqlSelect) {
this.debug = sqlSelect.debug();
this.name = sqlSelect.name();
this.tableAlias = toNull(sqlSelect.tableAlias());
this.extend = toNull(sqlSelect.extend());
this.having = toNull(sqlSelect.having());
this.where = toNull(sqlSelect.where());
this.columnMapping = toNull(sqlSelect.columnMapping());
this.query = toNull(sqlSelect.query());
}
public DRawSqlMeta(String name, String extend, String query, boolean debug,
String where, String having, String columnMapping) {
@@ -129,11 +115,4 @@ public class DRawSqlMeta {
}
}
private static String toNull(String s) {
if (s != null && s.equals("")){
return null;
} else {
return s;
}
}
}
@@ -36,6 +36,17 @@ public class GeneratedInsertJavaTime {
}
}
/**
* Instant support.
*/
public static class InstantDT extends Base {
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return JavaTimeUtils.toInstant(now);
}
}
/**
* LocalDateTime support.
*/
@@ -31,6 +31,22 @@ public class GeneratedUpdateJavaTime {
}
}
/**
* Instant support.
*/
public static class InstantDT extends Base {
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return JavaTimeUtils.toInstant(now);
}
@Override
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
return JavaTimeUtils.toInstant(now);
}
}
/**
* LocalDateTime support.
*/
@@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.persistence.PersistenceException;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
@@ -27,6 +28,7 @@ public class InsertTimestampFactory {
map.put(long.class, longTime);
if (classLoadConfig.isJavaTimePresent()) {
map.put(Instant.class, new GeneratedInsertJavaTime.InstantDT());
map.put(LocalDateTime.class, new GeneratedInsertJavaTime.LocalDT());
map.put(OffsetDateTime.class, new GeneratedInsertJavaTime.OffsetDT());
map.put(ZonedDateTime.class, new GeneratedInsertJavaTime.ZonedDT());
@@ -11,6 +11,13 @@ import java.time.ZonedDateTime;
*/
public class JavaTimeUtils {
/**
* Return the system millis time as a LocalDateTime.
*/
public static Object toInstant(long systemMillis) {
return Instant.ofEpochMilli(systemMillis);
}
/**
* Return the system millis time as a LocalDateTime.
*/
@@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.persistence.PersistenceException;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
@@ -27,6 +28,7 @@ public class UpdateTimestampFactory {
map.put(long.class, longTime);
if (classLoadConfig.isJavaTimePresent()) {
map.put(Instant.class, new GeneratedUpdateJavaTime.InstantDT());
map.put(LocalDateTime.class, new GeneratedUpdateJavaTime.LocalDT());
map.put(OffsetDateTime.class, new GeneratedUpdateJavaTime.OffsetDT());
map.put(ZonedDateTime.class, new GeneratedUpdateJavaTime.ZonedDT());
@@ -95,10 +95,6 @@ public class AnnotationFields extends AnnotationParser {
if (docEmbedded != null) {
prop.setDocStoreEmbedded(docEmbedded.doc());
}
DocStoreEmbedded docStoreEmbedded = get(prop, DocStoreEmbedded.class);
if (docStoreEmbedded != null) {
prop.setDocStoreEmbedded(docStoreEmbedded.doc());
}
if (prop instanceof DeployBeanPropertyAssocOne<?>) {
if (prop.isId() && !prop.isEmbedded()) {
@@ -1,8 +1,7 @@
package com.avaje.ebeaninternal.server.deploy.parse;
import com.avaje.ebean.annotation.Sql;
import com.avaje.ebean.annotation.SqlSelect;
import com.avaje.ebeaninternal.server.deploy.DRawSqlMeta;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
* Read the class level deployment annotations.
@@ -17,25 +16,8 @@ public class AnnotationSql extends AnnotationParser {
Class<?> cls = descriptor.getBeanType();
Sql sql = cls.getAnnotation(Sql.class);
if (sql != null) {
setSql(sql);
}
SqlSelect sqlSelect = cls.getAnnotation(SqlSelect.class);
if (sqlSelect != null) {
setSqlSelect(sqlSelect);
descriptor.setEntityType(BeanDescriptor.EntityType.SQL);
}
}
private void setSql(Sql sql) {
SqlSelect[] select = sql.select();
for (int i = 0; i < select.length; i++) {
setSqlSelect(select[i]);
}
}
private void setSqlSelect(SqlSelect sqlSelect) {
DRawSqlMeta rawSqlMeta = new DRawSqlMeta(sqlSelect);
descriptor.add(rawSqlMeta);
}
}
@@ -1,7 +1,6 @@
package com.avaje.ebeaninternal.server.lib;
/**
* This is the ShutdownHook that gets added to Runtime.
* It will try to shutdown the system cleanly when the JVM exits.
@@ -9,14 +8,14 @@ package com.avaje.ebeaninternal.server.lib;
*/
class ShutdownHook extends Thread {
ShutdownHook() {
}
ShutdownHook() {
}
/**
* Fired by the JVM Runtime on shutdown.
*/
public void run() {
ShutdownManager.shutdown();
}
/**
* Fired by the JVM Runtime on shutdown.
*/
public void run() {
ShutdownManager.shutdown();
}
}
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.persist;
import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
@@ -13,6 +14,7 @@ import com.avaje.ebean.config.dbplatform.DbType;
import com.avaje.ebeaninternal.api.BindParams;
import com.avaje.ebeaninternal.server.core.JsonExpressionHandler;
import com.avaje.ebeaninternal.server.core.Message;
import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone;
import com.avaje.ebeaninternal.server.type.DataBind;
import com.avaje.ebeaninternal.server.type.ScalarType;
import com.avaje.ebeaninternal.server.type.TypeManager;
@@ -34,14 +36,19 @@ public class Binder {
private final JsonExpressionHandler jsonExpressionHandler;
private final DataTimeZone dataTimeZone;
/**
* Set the PreparedStatement with which to bind variables to.
*/
public Binder(TypeManager typeManager, int asOfBindCount, boolean bindAsOfWithFromClause, JsonExpressionHandler jsonExpressionHandler) {
public Binder(TypeManager typeManager, int asOfBindCount, boolean bindAsOfWithFromClause,
JsonExpressionHandler jsonExpressionHandler, DataTimeZone dataTimeZone) {
this.typeManager = typeManager;
this.asOfBindCount = asOfBindCount;
this.bindAsOfWithFromClause = bindAsOfWithFromClause;
this.jsonExpressionHandler = jsonExpressionHandler;
this.dataTimeZone = dataTimeZone;
}
/**
@@ -91,6 +98,13 @@ public class Binder {
}
}
/**
* Bind the parameters to the preparedStatement returning the bind log.
*/
public String bind(BindParams bindParams, PreparedStatement statement) throws SQLException {
return bind(bindParams, new DataBind(dataTimeZone, statement));
}
/**
* Bind the list of positionedParameters in BindParams.
*/
@@ -392,4 +406,11 @@ public class Binder {
public JsonExpressionHandler getJsonExpressionHandler() {
return jsonExpressionHandler;
}
/**
* Create and return a DataBind for the statement.
*/
public DataBind dataBind(PreparedStatement stmt) {
return new DataBind(dataTimeZone, stmt);
}
}
@@ -559,17 +559,16 @@ public final class DefaultPersister implements Persister {
private void deleteList(List<?> beanList, Transaction t, boolean softDelete) {
for (int i = 0; i < beanList.size(); i++) {
deleteRecurse((EntityBean) beanList.get(i), t, softDelete);
//delete((EntityBean) beanList.get(i), t);
}
}
/**
* Delete by a List of Id's.
*/
public void deleteMany(Class<?> beanType, Collection<?> ids, Transaction transaction) {
public int deleteMany(Class<?> beanType, Collection<?> ids, Transaction transaction, boolean permanent) {
if (ids == null || ids.size() == 0) {
return;
return 0;
}
BeanDescriptor<?> descriptor = beanDescriptorManager.getBeanDescriptor(beanType);
@@ -580,19 +579,18 @@ public final class DefaultPersister implements Persister {
idList.add(descriptor.convertId(id));
}
delete(descriptor, null, idList, transaction, descriptor.isSoftDelete());
boolean softDelete = !permanent && descriptor.isSoftDelete();
return delete(descriptor, null, idList, transaction, softDelete);
}
/**
* Delete by Id.
*/
public int delete(Class<?> beanType, Object id, Transaction transaction) {
public int delete(Class<?> beanType, Object id, Transaction transaction, boolean permanent) {
BeanDescriptor<?> descriptor = beanDescriptorManager.getBeanDescriptor(beanType);
// convert to appropriate type if required
id = descriptor.convertId(id);
return delete(descriptor, id, null, transaction, descriptor.isSoftDelete());
boolean softDelete = !permanent && descriptor.isSoftDelete();
return delete(descriptor, id, null, transaction, softDelete);
}
/**
@@ -4,7 +4,6 @@ import com.avaje.ebeaninternal.api.BindParams;
import com.avaje.ebeaninternal.api.SpiCallableSql;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.PersistRequestCallableSql;
import com.avaje.ebeaninternal.server.type.DataBind;
import com.avaje.ebeaninternal.server.util.BindParamsParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -96,7 +95,7 @@ public class ExeCallableSql {
String bindLog = null;
if (!bindParams.isEmpty()) {
bindLog = binder.bind(bindParams, new DataBind(cstmt));
bindLog = binder.bind(bindParams, cstmt);
}
request.setBindLog(bindLog);
@@ -113,7 +113,7 @@ public class ExeOrmUpdate {
String bindLog = null;
if (!bindParams.isEmpty()) {
bindLog = binder.bind(bindParams, new DataBind(pstmt));
bindLog = binder.bind(bindParams, pstmt);
}
request.setBindLog(bindLog);
@@ -100,7 +100,7 @@ public class ExeUpdateSql {
String bindLog = null;
if (!bindParams.isEmpty()) {
bindLog = binder.bind(bindParams, new DataBind(pstmt));
bindLog = binder.bind(bindParams, pstmt);
}
request.setBindLog(bindLog);
@@ -1,14 +1,12 @@
package com.avaje.ebeaninternal.server.persist.dml;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.persistence.OptimisticLockException;
import com.avaje.ebeaninternal.api.DerivedRelationshipData;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.type.DataBind;
import javax.persistence.OptimisticLockException;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* Delete bean handler.
@@ -37,7 +35,7 @@ public class DeleteHandler extends DmlHandler {
} else {
pstmt = getPstmt(t, sql, false);
}
dataBind = new DataBind(pstmt);
dataBind = bind(pstmt);
meta.bind(persistRequest, this);
logSql(sql);
}
@@ -74,6 +74,13 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
return persistRequest;
}
/**
* Bind to the statement returning the DataBind.
*/
protected DataBind bind(PreparedStatement stmt) {
return new DataBind(persistRequest.getDataTimeZone(), stmt);
}
/**
* Get the sql and bind the statement.
*/
@@ -9,7 +9,6 @@ import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.persist.DmlUtil;
import com.avaje.ebeaninternal.server.type.DataBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -98,9 +97,7 @@ public class InsertHandler extends DmlHandler {
} else {
pstmt = getPstmt(t, sql, useGeneratedKeys);
}
dataBind = new DataBind(pstmt);
// bind the bean property values
dataBind = bind(pstmt);
meta.bind(this, bean, withId, persistRequest.isPublish());
logSql(sql);
@@ -4,7 +4,6 @@ import com.avaje.ebeaninternal.api.DerivedRelationshipData;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.api.SpiUpdatePlan;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.type.DataBind;
import javax.persistence.OptimisticLockException;
import java.sql.PreparedStatement;
@@ -47,8 +46,7 @@ public class UpdateHandler extends DmlHandler {
} else {
pstmt = getPstmt(t, sql, false);
}
dataBind = new DataBind(pstmt);
dataBind = bind(pstmt);
meta.bind(persistRequest, this, updatePlan);
setUpdateGenValues();
@@ -335,11 +335,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
DataBind dataBind = new DataBind(pstmt);
// bind keys for encrypted properties
queryPlan.bindEncryptedProperties(dataBind);
DataBind dataBind = queryPlan.bindEncryptedProperties(pstmt);
bindLog = predicates.bind(dataBind);
// executeQuery
@@ -105,7 +105,7 @@ public class CQueryDelete {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(new DataBind(pstmt));
bindLog = predicates.bind(pstmt);
rowCount = pstmt.executeUpdate();
long exeNano = System.nanoTime() - startNano;
@@ -12,7 +12,6 @@ import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.DbReadContext;
import com.avaje.ebeaninternal.server.type.DataBind;
import com.avaje.ebeaninternal.server.type.DataReader;
import com.avaje.ebeaninternal.server.type.RsetDataReader;
import org.slf4j.Logger;
@@ -144,10 +143,10 @@ public class CQueryFetchIds {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(new DataBind(pstmt));
bindLog = predicates.bind(pstmt);
ResultSet rset = pstmt.executeQuery();
dataReader = new RsetDataReader(rset);
dataReader = new RsetDataReader(request.getDataTimeZone(), rset);
boolean hitMaxRows = false;
boolean hasMoreRows = false;
@@ -9,11 +9,13 @@ import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
import com.avaje.ebeaninternal.server.type.DataBind;
import com.avaje.ebeaninternal.server.type.DataReader;
import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone;
import com.avaje.ebeaninternal.server.type.RsetDataReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.MessageDigest;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -65,6 +67,8 @@ public class CQueryPlan {
private final Class<?> beanType;
protected final DataTimeZone dataTimeZone;
/**
* Key used to identify the query plan in audit logging.
*/
@@ -76,6 +80,7 @@ public class CQueryPlan {
public CQueryPlan(OrmQueryRequest<?> request, SqlLimitResponse sqlRes, SqlTree sqlTree, boolean rawSql, String logWhereSql) {
this.server = request.getServer();
this.dataTimeZone = server.getDataTimeZone();
this.beanType = request.getBeanDescriptor().getBeanType();
this.stats = new CQueryPlanStats(this, server.isCollectQueryOrigins());
this.planKey = request.getQueryPlanKey();
@@ -100,6 +105,7 @@ public class CQueryPlan {
boolean rawSql, boolean rowNumberIncluded, String logWhereSql) {
this.server = request.getServer();
this.dataTimeZone = server.getDataTimeZone();
this.beanType = request.getBeanDescriptor().getBeanType();
this.stats = new CQueryPlanStats(this, server.isCollectQueryOrigins());
this.planKey = buildPlanKey(sql, rawSql, rowNumberIncluded, logWhereSql);
@@ -127,17 +133,21 @@ public class CQueryPlan {
}
public DataReader createDataReader(ResultSet rset) {
return new RsetDataReader(rset);
return new RsetDataReader(dataTimeZone, rset);
}
public void bindEncryptedProperties(DataBind dataBind) throws SQLException {
/**
* Bind keys for encrypted properties if necessary returning the DataBind.
*/
public DataBind bindEncryptedProperties(PreparedStatement stmt) throws SQLException {
DataBind dataBind = new DataBind(dataTimeZone, stmt);
if (encryptedProps != null) {
for (int i = 0; i < encryptedProps.length; i++) {
String key = encryptedProps[i].getEncryptKey().getStringValue();
dataBind.setString(key);
}
}
return dataBind;
}
public boolean isAutoTuned() {
@@ -24,9 +24,9 @@ public class CQueryPlanRawSql extends CQueryPlan {
this.rsetIndexPositions = createIndexPositions(request, sqlTree);
}
@Override
public DataReader createDataReader(ResultSet rset) {
return new RsetDataReaderIndexed(rset, rsetIndexPositions, isRowNumberIncluded());
return new RsetDataReaderIndexed(dataTimeZone, rset, rsetIndexPositions, isRowNumberIncluded());
}
private int[] createIndexPositions(OrmQueryRequest<?> request, SqlTree sqlTree) {
@@ -17,6 +17,7 @@ import com.avaje.ebeaninternal.server.expression.DefaultExpressionRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.HashSet;
@@ -125,6 +126,10 @@ public class CQueryPredicates {
this.idValue = query.getId();
}
public String bind(PreparedStatement stmt) throws SQLException {
return bind(binder.dataBind(stmt));
}
public String bind(DataBind dataBind) throws SQLException {
if (query.isVersionsBetween() && binder.isBindAsOfWithFromClause()) {
@@ -116,8 +116,7 @@ public class CQueryRowCount {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(new DataBind(pstmt));
bindLog = predicates.bind(pstmt);
rset = pstmt.executeQuery();
if (!rset.next()) {
@@ -81,7 +81,7 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
}
if (!bindParams.isEmpty()) {
bindLog = binder.bind(bindParams, new DataBind(pstmt));
bindLog = binder.bind(bindParams, pstmt);
}
if (request.isLogSql()) {
@@ -1,6 +1,5 @@
package com.avaje.ebeaninternal.server.transaction;
import com.avaje.ebean.event.BeanPersistListener;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.cluster.BinaryMessage;
import com.avaje.ebeaninternal.server.cluster.BinaryMessageList;
@@ -215,41 +214,24 @@ public class BeanPersistIds {
}
/**
* Notify the cache and local BeanPersistListener of this event that came
* from another server in the cluster.
* Notify the cache of this event that came from another server in the cluster.
*/
void notifyCacheAndListener() {
BeanPersistListener listener = beanDescriptor.getPersistListener();
// any change invalidates the query cache
beanDescriptor.queryCacheClear();
if (insertIds != null) {
if (listener != null) {
for (int i = 0; i < insertIds.size(); i++) {
listener.remoteInsert(insertIds.get(i));
}
}
}
if (updateIds != null) {
for (int i = 0; i < updateIds.size(); i++) {
Object id = updateIds.get(i);
beanDescriptor.cacheHandleDeleteById(id);
if (listener != null) {
listener.remoteUpdate(id);
}
}
}
if (deleteIds != null) {
for (int i = 0; i < deleteIds.size(); i++) {
Object id = deleteIds.get(i);
beanDescriptor.cacheHandleDeleteById(id);
if (listener != null) {
listener.remoteDelete(id);
}
}
}
}
}
@@ -1,5 +1,7 @@
package com.avaje.ebeaninternal.server.type;
import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.Reader;
@@ -9,16 +11,20 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
public class DataBind {
private final DataTimeZone dataTimeZone;
private final PreparedStatement pstmt;
private final StringBuilder bindLog = new StringBuilder();
private int pos;
public DataBind(PreparedStatement pstmt) {
public DataBind(DataTimeZone dataTimeZone, PreparedStatement pstmt) {
this.dataTimeZone = dataTimeZone;
this.pstmt = pstmt;
}
@@ -108,7 +114,12 @@ public class DataBind {
}
public void setTimestamp(Timestamp v) throws SQLException {
pstmt.setTimestamp(++pos, v);
Calendar timeZone = dataTimeZone.getTimeZone();
if (timeZone != null) {
pstmt.setTimestamp(++pos, v, timeZone);
} else {
pstmt.setTimestamp(++pos, v);
}
}
public void setTime(Time v) throws SQLException {
@@ -125,6 +125,8 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
private final boolean objectMapperPresent;
private final boolean java7Present;
// OPTIONAL ScalarTypes registered if Jackson/JsonNode is in the classpath
/**
@@ -153,6 +155,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
*/
public DefaultTypeManager(ServerConfig config, BootupClasses bootupClasses) {
this.java7Present = config.getClassLoadConfig().isJava7Present();
this.jsonDateTime = config.getJsonDateTime();
this.checkImmutable = new CheckImmutable(this);
this.reflectScalarBuilder = new ReflectionBasedTypeBuilder(this);
@@ -282,19 +285,33 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
/**
* This can return null if no matching ScalarType is found.
*/
@SuppressWarnings("unchecked")
public <T> ScalarType<T> getScalarType(Class<T> type) {
ScalarType<T> found = (ScalarType<T>) typeMap.get(type);
public ScalarType<?> getScalarType(Class<?> type) {
ScalarType<?> found = typeMap.get(type);
if (found == null) {
if (type.getName().equals("org.joda.time.LocalTime")) {
throw new IllegalStateException(
"ScalarType of Joda LocalTime not defined. You need to set ServerConfig.jodaLocalTimeMode to"
+ " either 'normal' or 'utc'. UTC is the old mode using UTC timezone but local time zone is now preferred as 'normal' mode.");
}
found = checkInterfaceTypes(type);
}
return found;
}
private ScalarType<?> checkInterfaceTypes(Class<?> type) {
if (java7Present) {
return checkJava7InterfaceTypes(type);
}
return null;
}
private ScalarType<?> checkJava7InterfaceTypes(Class<?> type) {
if (java.nio.file.Path.class.isAssignableFrom(type)) {
return typeMap.get(java.nio.file.Path.class);
}
return null;
}
public ScalarDataReader<?> getScalarDataReader(Class<?> propertyType, int sqlType) {
if (sqlType == 0) {
@@ -362,11 +379,11 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
* </p>
*/
@SuppressWarnings("unchecked")
public <T> ScalarType<T> getScalarType(Class<T> type, int jdbcType) {
public ScalarType<?> getScalarType(Class<?> type, int jdbcType) {
// File is a special Lob so check for that first
if (File.class.equals(type)) {
return (ScalarType<T>) fileType;
return fileType;
}
// check for Clob, LongVarchar etc ...
@@ -375,23 +392,23 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
ScalarType<?> scalarType = getLobTypes(jdbcType);
if (scalarType != null) {
// it is a specific Lob type...
return (ScalarType<T>) scalarType;
return scalarType;
}
scalarType = typeMap.get(type);
if (scalarType != null) {
if (jdbcType == 0 || scalarType.getJdbcType() == jdbcType) {
// matching type
return (ScalarType<T>) scalarType;
return scalarType;
}
}
// a util Date with jdbcType not matching server wide settings
if (type.equals(java.util.Date.class)) {
return (ScalarType<T>) extraTypeFactory.createUtilDate(jsonDateTime, jdbcType);
return extraTypeFactory.createUtilDate(jsonDateTime, jdbcType);
}
// a Calendar with jdbcType not matching server wide settings
if (type.equals(java.util.Calendar.class)) {
return (ScalarType<T>) extraTypeFactory.createCalendar(jsonDateTime, jdbcType);
return extraTypeFactory.createCalendar(jsonDateTime, jdbcType);
}
throw new IllegalArgumentException("Unmatched ScalarType for " + type + " jdbcType:" + jdbcType);
@@ -772,6 +789,11 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
}
protected void initialiseJavaTimeTypes(JsonConfig.DateTime mode, ServerConfig config) {
if (java7Present) {
typeMap.put(java.nio.file.Path.class, new ScalarTypePath());
}
if (config.getClassLoadConfig().isJavaTimePresent()) {
logger.debug("Registering java.time data types");
typeMap.put(java.time.LocalDate.class, new ScalarTypeLocalDate());
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.type;
import com.avaje.ebeaninternal.server.core.Message;
import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -14,6 +15,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
public class RsetDataReader implements DataReader {
@@ -23,11 +25,14 @@ public class RsetDataReader implements DataReader {
static final int stringInitialSize = 512;
private final DataTimeZone dataTimeZone;
private final ResultSet rset;
protected int pos;
public RsetDataReader(ResultSet rset) {
public RsetDataReader(DataTimeZone dataTimeZone, ResultSet rset) {
this.dataTimeZone = dataTimeZone;
this.rset = rset;
}
@@ -149,9 +154,13 @@ public class RsetDataReader implements DataReader {
return rset.getTime(pos());
}
public Timestamp getTimestamp() throws SQLException {
return rset.getTimestamp(pos());
Calendar cal = dataTimeZone.getTimeZone();
if (cal != null) {
return rset.getTimestamp(pos(), cal);
} else {
return rset.getTimestamp(pos());
}
}
public String getStringFromStream() throws SQLException {
@@ -1,13 +1,15 @@
package com.avaje.ebeaninternal.server.type;
import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone;
import java.sql.ResultSet;
public class RsetDataReaderIndexed extends RsetDataReader {
private final int[] rsetIndexPositions;
public RsetDataReaderIndexed(ResultSet rset, int[] rsetIndexPositions, boolean rowNumberIncluded) {
super(rset);
public RsetDataReaderIndexed(DataTimeZone dataTimeZone, ResultSet rset, int[] rsetIndexPositions, boolean rowNumberIncluded) {
super(dataTimeZone, rset);
if (!rowNumberIncluded) {
this.rsetIndexPositions = rsetIndexPositions;
} else {
@@ -6,7 +6,6 @@ import java.sql.Timestamp;
import java.sql.Types;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
/**
* ScalarType for java.sql.Timestamp.
@@ -24,7 +23,7 @@ public class ScalarTypeLocalDateTime extends ScalarTypeBaseDateTime<LocalDateTim
@Override
public long convertToMillis(LocalDateTime value) {
return value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
return Timestamp.valueOf(value).getTime();
}
@Override
@@ -33,20 +32,18 @@ public class ScalarTypeLocalDateTime extends ScalarTypeBaseDateTime<LocalDateTim
}
@Override
protected String toJsonISO8601(LocalDateTime value) {
return value.atZone(ZoneId.systemDefault()).toInstant().toString();
protected String toJsonISO8601(LocalDateTime dateTime) {
return dateTime.atZone(ZoneId.systemDefault()).toInstant().toString();
}
@Override
public LocalDateTime convertFromTimestamp(Timestamp ts) {
return ts.toLocalDateTime();
public LocalDateTime convertFromTimestamp(Timestamp timestamp) {
return timestamp.toLocalDateTime();
}
@Override
public Timestamp convertToTimestamp(LocalDateTime t) {
ZonedDateTime zonedDateTime = t.atZone(ZoneId.systemDefault());
return Timestamp.from(zonedDateTime.toInstant());
public Timestamp convertToTimestamp(LocalDateTime dateTime) {
return Timestamp.valueOf(dateTime);
}
@Override
@@ -0,0 +1,40 @@
package com.avaje.ebeaninternal.server.type;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* ScalarType for java.nio.file.Path which converts to and from a VARCHAR database column.
*/
public class ScalarTypePath extends ScalarTypeBaseVarchar<Path> {
public ScalarTypePath() {
super(Path.class);
}
@Override
public Path convertFromDbString(String dbValue) {
try {
return Paths.get(new URI(dbValue));
} catch (URISyntaxException e) {
throw new RuntimeException("Error with Path URI [" + dbValue + "] " + e);
}
}
@Override
public String convertToDbString(Path beanValue) {
return beanValue.toUri().toString();
}
@Override
public String formatValue(Path path) {
return convertToDbString(path);
}
@Override
public Path parse(String value) {
return convertFromDbString(value);
}
}
@@ -42,14 +42,14 @@ public interface TypeManager {
/**
* Return the ScalarType for a given logical type.
*/
<T> ScalarType<T> getScalarType(Class<T> type);
ScalarType<?> getScalarType(Class<?> type);
/**
* For java.util.Date and java.util.Calendar additionally pass the jdbc type
* that you would like the ScalarType to map to. This is because these types
* can map to different java.sql.Types depending on the property.
*/
<T> ScalarType<T> getScalarType(Class<T> type, int jdbcType);
ScalarType<?> getScalarType(Class<?> type, int jdbcType);
/**
* Create a ScalarType for an Enum using a mapping (rather than JPA Ordinal
@@ -30,7 +30,6 @@ public class EbeanServer_deleteAllByIdTest {
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(1);
assertThat(loggedSql.get(0)).contains("delete from e_basicver where id in (?,?,?)");
}
@Test
@@ -59,6 +58,53 @@ public class EbeanServer_deleteAllByIdTest {
assertThat(loggedSql.get(0)).contains("delete from e_basicver where id in (?,?,?)");
}
@Test
public void deleteAllPermanentById() {
List<EBasicVer> someBeans = beans(3);
Ebean.saveAll(someBeans);
List<Integer> ids = new ArrayList<Integer>();
for (EBasicVer someBean : someBeans) {
ids.add(someBean.getId());
}
LoggedSqlCollector.start();
Ebean.deleteAllPermanent(EBasicVer.class, ids);
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(1);
assertThat(loggedSql.get(0)).contains("delete from e_basicver where id in (?,?,?)");
}
@Test
public void deleteAllPermanentById_withTransaction() {
List<EBasicVer> someBeans = beans(3);
Ebean.saveAll(someBeans);
List<Integer> ids = new ArrayList<Integer>();
for (EBasicVer someBean : someBeans) {
ids.add(someBean.getId());
}
EbeanServer server = Ebean.getDefaultServer();
// act
LoggedSqlCollector.start();
Transaction txn = server.beginTransaction();
try {
server.deleteAllPermanent(EBasicVer.class, ids, txn);
txn.commit();
} finally {
txn.end();
}
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(1);
assertThat(loggedSql.get(0)).contains("delete from e_basicver where id in (?,?,?)");
}
private List<EBasicVer> beans(int count) {
List<EBasicVer> beans = new ArrayList<EBasicVer>();
for (int i = 0; i < count; i++) {
@@ -12,10 +12,9 @@ import static org.assertj.core.api.Assertions.assertThat;
public class EbeanServer_deleteByIdTest {
@Test
public void deleteAllById() {
public void deleteById() {
EBasicVer someBean = bean("foo1");
Ebean.save(someBean);
// act
@@ -28,7 +27,23 @@ public class EbeanServer_deleteByIdTest {
}
@Test
public void deleteAllById_withTransaction() {
public void deletePermanentById() {
EBasicVer someBean = bean("foo1");
Ebean.save(someBean);
// act
LoggedSqlCollector.start();
Ebean.deletePermanent(EBasicVer.class, someBean.getId());
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(1);
assertThat(loggedSql.get(0)).contains("delete from e_basicver where id = ?");
}
@Test
public void deleteById_withTransaction() {
EBasicVer someBean = bean("foo1");
Ebean.save(someBean);
@@ -48,6 +63,27 @@ public class EbeanServer_deleteByIdTest {
assertThat(loggedSql.get(0)).contains("delete from e_basicver where id = ?");
}
@Test
public void deletePermanentById_withTransaction() {
EBasicVer someBean = bean("foo2");
Ebean.save(someBean);
EbeanServer server = Ebean.getDefaultServer();
// act
LoggedSqlCollector.start();
Transaction txn = server.beginTransaction();
try {
server.deletePermanent(EBasicVer.class, someBean.getId(), txn);
txn.commit();
} finally {
txn.end();
}
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(1);
assertThat(loggedSql.get(0)).contains("delete from e_basicver where id = ?");
}
private EBasicVer bean(String name) {
EBasicVer bean = new EBasicVer();
bean.setName(name);
@@ -0,0 +1,43 @@
package com.avaje.ebean;
import org.junit.Test;
import static org.junit.Assert.*;
public class PropertyTest {
@Test
public void equals() throws Exception {
assertEquals(prop("foo", true), prop("foo", true));
}
@Test
public void diff_basic() throws Exception {
assertNotEquals(prop("foo", true), prop("bar", true));
assertNotEquals(prop("foo", true), prop("foo", false));
assertNotEquals(prop("foo", false), prop("foo", true));
}
@Test
public void diff_nulls() throws Exception {
assertEquals(prop("foo", true, "nulls", "high"), prop("foo", true, "nulls", "high"));
assertEquals(prop("foo", true, "nulls", "low"), prop("foo", true, "nulls", "low"));
assertNotEquals(prop("foo", true), prop("foo", true, "nulls", "high"));
assertNotEquals(prop("foo", true, "nulls", "high"), prop("foo", true));
assertNotEquals(prop("foo", true, "nulls", "high"), prop("foo", true, "nulls", "low"));
assertNotEquals(prop("foo", true, "nulls", "low"), prop("foo", true, "nulls", "high"));
}
private OrderBy.Property prop(String name, boolean asc) {
return new OrderBy.Property(name, asc, null, null);
}
private OrderBy.Property prop(String name, boolean asc, String nulls, String highLow) {
return new OrderBy.Property(name, asc, nulls, highLow);
}
}
@@ -89,7 +89,7 @@ public class TestTypeManager extends BaseTestCase {
String val;
public DummyDataReader(String val) {
super(null);
super(null, null);
this.val = val;
}
@@ -17,6 +17,7 @@ import com.avaje.ebean.text.json.JsonContext;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.query.CQuery;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
@@ -44,6 +45,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
}
@Override
public DataTimeZone getDataTimeZone() {
return null;
}
@Override
public SpiServer getPluginApi() {
return null;
@@ -569,6 +575,16 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
return 0;
}
@Override
public int deletePermanent(Class<?> beanType, Object id) {
return 0;
}
@Override
public int deletePermanent(Class<?> beanType, Object id, Transaction transaction) {
return 0;
}
@Override
public int execute(SqlUpdate updSql) {
return 0;
@@ -750,13 +766,23 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
}
@Override
public void deleteAll(Class<?> beanType, Collection<?> ids) {
public int deleteAll(Class<?> beanType, Collection<?> ids) {
return 0;
}
@Override
public void deleteAll(Class<?> beanType, Collection<?> ids, Transaction transaction) {
public int deleteAll(Class<?> beanType, Collection<?> ids, Transaction transaction) {
return 0;
}
@Override
public int deleteAllPermanent(Class<?> beanType, Collection<?> ids) {
return 0;
}
@Override
public int deleteAllPermanent(Class<?> beanType, Collection<?> ids, Transaction transaction) {
return 0;
}
@Override
@@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import org.junit.Test;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
@@ -14,6 +15,16 @@ public class InsertTimestampFactoryTest {
InsertTimestampFactory factory = new InsertTimestampFactory(new ClassLoadConfig());
@Test
public void test_createdTimestamp_Instant() {
DeployBeanProperty prop = new DeployBeanProperty(null, Instant.class, null, null);
GeneratedProperty insertTimestamp = factory.createInsertTimestamp(prop);
Object value = insertTimestamp.getInsertValue(null, null, System.currentTimeMillis());
assertTrue(value instanceof Instant);
}
@Test
public void test_createdTimestamp_LocalDateTime() {
@@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import org.junit.Test;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
@@ -15,6 +16,16 @@ public class UpdateTimestampFactoryTest {
UpdateTimestampFactory factory = new UpdateTimestampFactory(new ClassLoadConfig());
@Test
public void test_createdTimestamp_Instant() {
DeployBeanProperty prop = new DeployBeanProperty(null, Instant.class, null, null);
GeneratedProperty generatedProperty = factory.createUpdateTimestamp(prop);
Object value = generatedProperty.getInsertValue(null, null, System.currentTimeMillis());
assertTrue(value instanceof Instant);
}
@Test
public void test_createdTimestamp_LocalDateTime() {
@@ -16,16 +16,25 @@ public class ScalarTypeLocalDateTimeTest {
LocalDateTime warmUp = LocalDateTime.now();
@Test
public void testConvertToMillis() throws Exception {
public void testNowToMillis() throws Exception {
warmUp.hashCode();
long now = System.currentTimeMillis();
long toMillis = type.convertToMillis(LocalDateTime.now());
assertTrue(toMillis - now < 30);
}
@Test
public void testConvertToMillis() throws Exception {
LocalDateTime now = LocalDateTime.now();
long asMillis = type.convertToMillis(now);
LocalDateTime fromMillis = type.convertFromMillis(asMillis);
assertEquals(now, fromMillis);
}
@Test
public void testConvertFromTimestamp() throws Exception {
@@ -0,0 +1,36 @@
package com.avaje.ebeaninternal.server.type;
import org.junit.Test;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.junit.Assert.assertEquals;
public class ScalarTypePathTest {
private ScalarTypePath type = new ScalarTypePath();
@Test
public void convertFromDbString() throws Exception {
Path path = Paths.get("/tmp");
String asString = type.convertToDbString(path);
Path converted = type.convertFromDbString(asString);
assertEquals(path, converted);
}
@Test
public void formatAndParse() throws Exception {
Path path = Paths.get("/tmp");
String asString = type.formatValue(path);
Path converted = type.parse(asString);
assertEquals(path, converted);
}
}
@@ -82,7 +82,6 @@ public class DocLinkTest extends BaseTestCase {
Ebean.delete(link1);
}
@Test
public void testDeletePermanent_whenPublished2() {
@@ -31,16 +31,19 @@ public class TestGeneratedProperties extends BaseTestCase {
assertNotNull(bean.getZdtUpdated());
assertNotNull(bean.getLongCreated());
assertNotNull(bean.getLongUpdated());
assertNotNull(bean.getInstantCreated());
assertNotNull(bean.getInstantUpdated());
assertThat(bean.getWhenCreated().toInstant().toEpochMilli()).isEqualTo(bean.getLongCreated());
assertThat(bean.getWhenModified().toInstant().toEpochMilli()).isEqualTo(bean.getLongCreated());
assertThat(bean.getWhenModified().toInstant().toEpochMilli()).isEqualTo(bean.getLongCreated());
bean.setName("updating...");
Ebean.save(bean);
assertThat(bean.getWhenModified()).isNotEqualTo(bean.getLongCreated());
assertThat(bean.getWhenModified().toInstant().toEpochMilli()).isEqualTo(bean.getLongUpdated());
assertThat(bean.getInstantUpdated().toEpochMilli()).isEqualTo(bean.getLongUpdated());
assertThat(bean.getInstantCreated().toEpochMilli()).isEqualTo(bean.getLongCreated());
Ebean.delete(bean);
}
@@ -9,6 +9,7 @@ import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Version;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
@@ -54,6 +55,12 @@ public class EGenProps {
@UpdatedTimestamp
ZonedDateTime zdtUpdated;
@CreatedTimestamp
Instant instantCreated;
@UpdatedTimestamp
Instant instantUpdated;
@CreatedTimestamp
long longCreated;
@@ -179,4 +186,20 @@ public class EGenProps {
public void setLongUpdated(long longUpdated) {
this.longUpdated = longUpdated;
}
public Instant getInstantCreated() {
return instantCreated;
}
public void setInstantCreated(Instant instantCreated) {
this.instantCreated = instantCreated;
}
public Instant getInstantUpdated() {
return instantUpdated;
}
public void setInstantUpdated(Instant instantUpdated) {
this.instantUpdated = instantUpdated;
}
}
@@ -3,7 +3,6 @@ package com.avaje.tests.model.basic;
import com.avaje.ebean.annotation.ChangeLog;
import com.avaje.ebean.annotation.DocEmbedded;
import com.avaje.ebean.annotation.DocStore;
import com.avaje.ebean.annotation.DocStoreEmbedded;
import com.avaje.ebean.annotation.Formula;
import com.avaje.ebean.annotation.WhenCreated;
import com.avaje.ebean.annotation.Where;
@@ -102,7 +101,7 @@ public class Order implements Serializable {
@Where(clause = "${ta}.id > 0")
@OneToMany(cascade = CascadeType.ALL, mappedBy = "order")
@OrderBy("id asc, orderQty asc, cretime desc")
@DocStoreEmbedded
@DocEmbedded
List<OrderDetail> details;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "order")
@@ -17,6 +17,13 @@ public class Album extends BaseModel {
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private Cover cover;
public Album(String name) {
this.name = name;
}
public Album() {
}
public String getName() {
return this.name;
}
@@ -25,6 +25,13 @@ public class Cover extends Model {
protected String s3Url;
public Cover() {
}
public Cover(String s3Url) {
this.s3Url = s3Url;
}
public String getS3Url() {
return this.s3Url;
}
@@ -0,0 +1,263 @@
package com.avaje.tests.model.onetoone.album;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.Transaction;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class DeleteById_SoftDelete_Tests {
@Test
public void ebean_deleteById_when_softDelete() {
Cover cover = new Cover("a1");
cover.save();
LoggedSqlCollector.start();
Ebean.delete(Cover.class, cover.getId());
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("update cover set deleted=true where id = ?");
cover.deletePermanent();
}
@Test
public void deleteById_when_softDelete() {
Cover cover = new Cover("a1");
cover.save();
LoggedSqlCollector.start();
Ebean.getDefaultServer().delete(Cover.class, cover.getId(), null);
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("update cover set deleted=true where id = ?");
cover.deletePermanent();
}
@Test
public void deletePermanentById_when_softDelete() {
Cover cover = new Cover("a2");
cover.save();
LoggedSqlCollector.start();
Ebean.getDefaultServer().deletePermanent(Cover.class, cover.getId(), null);
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("delete from cover where id = ?");
}
@Test
public void deleteAllById_when_softDelete() {
EbeanServer server = Ebean.getDefaultServer();
List<Cover> beans = beans(2);
server.saveAll(beans);
LoggedSqlCollector.start();
server.deleteAll(Cover.class, ids(beans));
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("update cover set deleted=true where id in (?,?)");
}
@Test
public void deleteAllById_when_softDelete_withTransaction() {
EbeanServer server = Ebean.getDefaultServer();
List<Cover> beans = beans(2);
server.saveAll(beans);
LoggedSqlCollector.start();
Transaction transaction = server.beginTransaction();
try {
server.deleteAll(Cover.class, ids(beans), transaction);
server.commitTransaction();
} finally {
server.endTransaction();
}
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("update cover set deleted=true where id in (?,?)");
}
@Test
public void ebean_deleteAllPermanentById_when_softDelete() {
List<Cover> beans = beans(2);
Ebean.saveAll(beans);
LoggedSqlCollector.start();
Ebean.deleteAllPermanent(Cover.class, ids(beans));
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("delete from cover where id in (?,?)");
}
@Test
public void deleteAllPermanentById_when_softDelete() {
EbeanServer server = Ebean.getDefaultServer();
List<Cover> beans = beans(2);
server.saveAll(beans);
LoggedSqlCollector.start();
server.deleteAllPermanent(Cover.class, ids(beans));
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("delete from cover where id in (?,?)");
}
@Test
public void deleteAllPermanentById_when_softDelete_withTransaction() {
EbeanServer server = Ebean.getDefaultServer();
List<Cover> beans = beans(2);
server.saveAll(beans);
LoggedSqlCollector.start();
Transaction transaction = server.beginTransaction();
try {
server.deleteAllPermanent(Cover.class, ids(beans), transaction);
server.commitTransaction();
} finally {
server.endTransaction();
}
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("delete from cover where id in (?,?)");
}
@Test
public void ebean_deleteAll_when_softDelete() {
List<Cover> beans = beans(2);
Ebean.saveAll(beans);
LoggedSqlCollector.start();
Ebean.deleteAll(beans);
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("update cover set s3url=?, deleted=? where id=?");
}
@Test
public void deleteAll_when_softDelete() {
EbeanServer server = Ebean.getDefaultServer();
List<Cover> beans = beans(2);
server.saveAll(beans);
LoggedSqlCollector.start();
server.deleteAll(beans);
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("update cover set s3url=?, deleted=? where id=?");
}
@Test
public void deleteAll_when_softDelete_withTransaction() {
EbeanServer server = Ebean.getDefaultServer();
List<Cover> beans = beans(2);
server.saveAll(beans);
LoggedSqlCollector.start();
Transaction transaction = server.beginTransaction();
try {
server.deleteAll(beans, transaction);
server.commitTransaction();
} finally {
server.endTransaction();
}
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("update cover set s3url=?, deleted=? where id=?");
}
@Test
public void deleteAllPermanent_when_softDelete() {
EbeanServer server = Ebean.getDefaultServer();
List<Cover> beans = beans(2);
server.saveAll(beans);
LoggedSqlCollector.start();
server.deleteAllPermanent(beans);
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("delete from cover where id=?");
}
@Test
public void deleteAllPermanent_when_softDelete_withTransaction() {
EbeanServer server = Ebean.getDefaultServer();
List<Cover> beans = beans(2);
server.saveAll(beans);
LoggedSqlCollector.start();
Transaction transaction = server.beginTransaction();
try {
server.deleteAllPermanent(beans, transaction);
server.commitTransaction();
} finally {
server.endTransaction();
}
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("delete from cover where id=?");
}
private List<Long> ids(List<Cover> beans) {
List<Long> ids = new ArrayList<Long>();
for (Cover someBean : beans) {
ids.add(someBean.getId());
}
return ids;
}
private List<Cover> beans(int count) {
List<Cover> beans = new ArrayList<Cover>();
for (int i = 0; i < count; i++) {
beans.add(new Cover("delById " + i));
}
return beans;
}
}
@@ -39,7 +39,7 @@ public class TestOneToOneHardDelete {
assertThat(sql.get(1)).contains("update album set deleted=?, last_update=? where id=?");
assertThat(sql.get(2)).contains("update cover set deleted=? where id=?");
Album found2 = Album.find.where().includeSoftDeletes().findUnique();
Album found2 = Album.find.where().setIncludeSoftDeletes().findUnique();
LoggedSqlCollector.start();
@@ -4,6 +4,7 @@ import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Version;
import java.nio.file.Path;
import java.time.*;
@Entity
@@ -41,6 +42,8 @@ public class SomeNewTypesBean {
ZoneOffset zoneOffset;
Path path;
public Long getId() {
return id;
}
@@ -144,4 +147,12 @@ public class SomeNewTypesBean {
public void setZoneOffset(ZoneOffset zoneOffset) {
this.zoneOffset = zoneOffset;
}
public Path getPath() {
return path;
}
public void setPath(Path path) {
this.path = path;
}
}
@@ -3,6 +3,7 @@ package com.avaje.tests.rawsql;
import java.util.List;
import java.util.concurrent.ExecutionException;
import com.avaje.tests.model.basic.Order;
import org.junit.Assert;
import org.junit.Test;
@@ -17,6 +18,8 @@ import com.avaje.ebean.RawSqlBuilder;
import com.avaje.tests.model.basic.Customer;
import com.avaje.tests.model.basic.ResetBasicData;
import static org.assertj.core.api.Assertions.assertThat;
public class TestRawSqlOrmQuery extends BaseTestCase {
@Test
@@ -107,4 +110,71 @@ public class TestRawSqlOrmQuery extends BaseTestCase {
}
}
@Test
public void testPaging_with_existingRawSqlOrderBy_expect_id_appendToOrderBy() {
ResetBasicData.reset();
RawSql rawSql = RawSqlBuilder.parse("select o.id, o.order_date, o.ship_date from o_order o order by o.ship_date desc nulls last")
.columnMapping("o.id", "id")
.columnMapping("o.order_date", "orderDate")
.columnMapping("o.ship_date", "shipDate")
.create();
Query<Order> query = Ebean.find(Order.class);
query.setUseCache(false).setUseQueryCache(false);
query.setRawSql(rawSql);
query.setMaxRows(100);
query.findList();
assertThat(query.getGeneratedSql()).contains("order by o.ship_date desc nulls last, o.id limit 100");
}
@Test
public void testPaging_when_setOrderBy_expect_id_appendToOrderBy() {
ResetBasicData.reset();
RawSql rawSql = RawSqlBuilder.parse("select o.id, o.order_date, o.ship_date from o_order o order by o.ship_date desc nulls last")
.columnMapping("o.id", "id")
.columnMapping("o.order_date", "orderDate")
.columnMapping("o.ship_date", "shipDate")
.create();
Query<Order> query = Ebean.find(Order.class);
query.setUseCache(false).setUseQueryCache(false);
query.setRawSql(rawSql);
query.setMaxRows(100);
query.order("coalesce(shipDate, now()) desc");
query.findList();
assertThat(query.getGeneratedSql()).contains("order by coalesce(o.ship_date, now()) desc, o.id limit 100");
}
@Test
public void testPaging_when_setOrderBy_containsId_expect_leaveAsIs() {
ResetBasicData.reset();
RawSql rawSql = RawSqlBuilder.parse("select o.id, o.order_date, o.ship_date from o_order o order by o.ship_date desc nulls last")
.columnMapping("o.id", "id")
.columnMapping("o.order_date", "orderDate")
.columnMapping("o.ship_date", "shipDate")
.create();
Query<Order> query = Ebean.find(Order.class);
query.setUseCache(false).setUseQueryCache(false);
query.setRawSql(rawSql);
query.setMaxRows(100);
query.order("id desc");
PagedList<Order> pagedList = query.findPagedList();
pagedList.getList();
pagedList.getTotalRowCount();
assertThat(query.getGeneratedSql()).contains("order by o.id desc limit 100");
}
}
@@ -0,0 +1,100 @@
package com.avaje.tests.timezone;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Transaction;
import org.junit.Ignore;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.TimeZone;
/**
* Must run this manually with various JVM timezones for insert and fetch.
*/
public class TimezoneTests {
private final Timestamp nowTs = new Timestamp(1460000000000L);
public TimezoneTests() {
}
@Ignore
@Test
public void rawJdbc() throws SQLException {
// insert("Local");
// insert("UTC");
// insert("America/Los_Angeles");
// System.out.println("Local");
// fetch();
// System.out.println("UTC");
// setZone("UTC");
// fetch();
System.out.println("LA");
setZone("America/Los_Angeles");
fetch();
}
private void fetch() throws SQLException {
Transaction transaction = Ebean.beginTransaction();
Connection connection = transaction.getConnection();
PreparedStatement statement = connection.prepareStatement("select * from tztest");
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
System.out.println(" zone:"+resultSet.getString("zone"));
System.out.println(" ts:"+tsof(resultSet.getTimestamp("ts")));
System.out.println(" tstz:"+tsof(resultSet.getTimestamp("tstz")));
System.out.println(" ts1:"+tsof(resultSet.getTimestamp("ts1", cal())));
System.out.println("tstz1:"+tsof(resultSet.getTimestamp("tstz1", cal())));
}
System.out.println("");
resultSet.close();
statement.close();
transaction.end();
}
private String tsof(Timestamp timestamp) {
return ""+timestamp.getTime()+", "+timestamp.toString()+", "+timestamp.toInstant();
}
private void setZone(String zone) {
TimeZone.setDefault(TimeZone.getTimeZone(zone));
}
private void insert(String zone) throws SQLException {
String insert = "insert into tztest (zone, ts, tstz, ts1, tstz1) values (?,?,?,?,?)";
if (!zone.equalsIgnoreCase("local")) {
setZone(zone);
}
Transaction transaction = Ebean.beginTransaction();
Connection connection = transaction.getConnection();
PreparedStatement statement = connection.prepareStatement(insert);
statement.setString(1, zone);
statement.setTimestamp(2, nowTs);
statement.setTimestamp(3, nowTs);
statement.setTimestamp(4, nowTs, cal());
statement.setTimestamp(5, nowTs, cal());
statement.executeUpdate();
transaction.commit();
}
private Calendar cal() {
Calendar instance = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
//Calendar instance = Calendar.getInstance(TimeZone.getDefault());
return (Calendar) instance.clone();
}
}
@@ -0,0 +1,53 @@
package com.avaje.tests.timezone;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.sql.Timestamp;
@Entity
@Table(name="tz_bean")
public class TzBean {
@Id
Long id;
@Column(name = "mode")
String mode;
Timestamp ts;
Timestamp tstz;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getMode() {
return mode;
}
public void setMode(String mode) {
this.mode = mode;
}
public Timestamp getTs() {
return ts;
}
public void setTs(Timestamp ts) {
this.ts = ts;
}
public Timestamp getTstz() {
return tstz;
}
public void setTstz(Timestamp tstz) {
this.tstz = tstz;
}
}
@@ -0,0 +1,94 @@
package com.avaje.tests.timezone;
import com.avaje.ebean.Ebean;
import org.junit.Ignore;
import org.junit.Test;
import java.sql.Timestamp;
import java.util.List;
import java.util.TimeZone;
//import java.util.TimeZone;
// Postgres
// manually create table
// create table tz_bean (id serial, mode varchar(40), ts timestamp, tstz timestamp with time zone);
// MySql
// manually create table (note there is no timestamp with time zone type)
// create table tz_bean (id serial, mode varchar(40), ts timestamp, tstz timestamp);
// Oracle
// create table tz_bean (id integer, run_mode varchar(40), ts timestamp, tstz timestamp);
/**
* Must run this test manually with various time zones for insert and fetch.
*/
public class TzBeanTests {
private final Timestamp nowTs = new Timestamp(1460000000000L);
public TzBeanTests() {
}
/**
* Run this 5 times with different modes.
*/
@Ignore
@Test
public void insert_one_at_a_time() {
//String mode = "noCal + local tz";
// String mode = "noCal + LA";
// TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
// String mode = "noCal + UTC";
// TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
// System.setProperty("ebean.dataTimeZone", "UTC");
// TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
// String mode = "UTC + LA";
System.setProperty("ebean.dataTimeZone", "UTC");
String mode = "UTC + NZST";
TzBean bean = new TzBean();
bean.setMode(mode);
bean.setTs(nowTs);
bean.setTstz(nowTs);
Ebean.save(bean);
}
/**
* In separate JVM/execution fetch the beans with various timezones etc.
*/
@Ignore
@Test
public void fetch_beans() {
// set the jvm timezone
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
// TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
// set the Calendar time zone to use in JDBC calls
System.setProperty("ebean.dataTimeZone", "UTC");
List<TzBean> list = Ebean.find(TzBean.class)
.findList();
for (TzBean bean : list) {
System.out.println(" mode:"+bean.getMode());
System.out.println(" ts:"+tsof(bean.getTs()));
System.out.println(" tstz:"+tsof(bean.getTstz()));
}
}
private String tsof(Timestamp timestamp) {
return ""+timestamp.getTime()+", "+timestamp.toString()+", "+timestamp.toInstant();
}
}
@@ -6,6 +6,7 @@ import com.avaje.tests.model.types.SomeNewTypesBean;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Paths;
import java.time.*;
import java.util.List;
@@ -30,6 +31,7 @@ public class TestNewTypes extends BaseTestCase {
bean.setZoneId(ZoneId.systemDefault());
bean.setZoneOffset(ZonedDateTime.now().getOffset());
bean.setYearMonth(YearMonth.of(2014, 9));
bean.setPath(Paths.get("/tmp"));
Ebean.save(bean);
@@ -69,6 +71,9 @@ public class TestNewTypes extends BaseTestCase {
list = Ebean.find(SomeNewTypesBean.class).where().le("month", Month.SEPTEMBER).findList();
assertTrue(!list.isEmpty());
list = Ebean.find(SomeNewTypesBean.class).where().eq("path", Paths.get("/tmp")).findList();
assertTrue(!list.isEmpty());
SomeNewTypesBean fetched = Ebean.find(SomeNewTypesBean.class, bean.getId());
assertEquals(bean.getZoneId(), fetched.getZoneId());
@@ -80,6 +85,7 @@ public class TestNewTypes extends BaseTestCase {
assertEquals(bean.getLocalDateTime(), fetched.getLocalDateTime());
assertEquals(bean.getOffsetDateTime(), fetched.getOffsetDateTime());
assertEquals(bean.getInstant(), fetched.getInstant());
assertEquals(bean.getPath(), fetched.getPath());
String asJson = Ebean.json().toJson(fetched);
@@ -94,6 +100,7 @@ public class TestNewTypes extends BaseTestCase {
assertEquals(bean.getLocalDateTime(), toBean.getLocalDateTime());
assertEquals(bean.getOffsetDateTime(), toBean.getOffsetDateTime());
assertEquals(bean.getInstant(), toBean.getInstant());
assertEquals(bean.getPath(), toBean.getPath());
}
@@ -115,6 +122,7 @@ public class TestNewTypes extends BaseTestCase {
assertNull(fetched.getLocalDateTime());
assertNull(fetched.getOffsetDateTime());
assertNull(fetched.getInstant());
assertNull(fetched.getPath());
}
}
@@ -44,6 +44,28 @@ public class TestOrderByParse extends BaseTestCase {
assertFalse(o1.containsProperty("junk"));
}
@Test
public void parseNullsHigh() {
OrderBy<Object> o1 = new OrderBy<Object>("id desc nulls high");
assertTrue(o1.getProperties().size() == 1);
assertTrue(o1.getProperties().get(0).getProperty().equals("id"));
assertTrue(!o1.getProperties().get(0).isAscending());
assertTrue(o1.toStringFormat().equals("id desc nulls high"));
}
@Test
public void parseNullsHigh_with_second() {
OrderBy<Object> o1 = new OrderBy<Object>("id desc nulls high, name");
assertTrue(o1.getProperties().size() == 2);
assertTrue(o1.getProperties().get(0).getProperty().equals("id"));
assertTrue(!o1.getProperties().get(0).isAscending());
assertTrue(o1.toStringFormat().equals("id desc nulls high, name"));
assertTrue(o1.getProperties().get(1).getProperty().equals("name"));
assertTrue(o1.getProperties().get(1).isAscending());
}
@Test
public void testParsingTwo() {
+4
View File
@@ -102,6 +102,7 @@ datasource.hsqldb.databaseDriver=org.hsqldb.jdbcDriver
datasource.mysql.username=unit
datasource.mysql.password=unit
datasource.mysql.databaseUrl=jdbc:mysql://127.0.0.1:3306/unit
#datasource.mysql.databaseUrl=jdbc:mysql://127.0.0.1:3306/unit?useLegacyDatetimeCode=false
datasource.mysql.databaseDriver=com.mysql.jdbc.Driver
@@ -110,6 +111,9 @@ datasource.ora.password=unit
datasource.ora.databaseUrl=jdbc:oracle:thin:@//127.0.0.1:1521/orcl
datasource.ora.databaseDriver=oracle.jdbc.driver.OracleDriver
## set this to use timestamp rather than timestamptz
#ebean.postgres.timestamp=timestamp
datasource.pg.username=unit
datasource.pg.password=unit
datasource.pg.databaseUrl=jdbc:postgresql://127.0.0.1:5432/unit