mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
86
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
723ae6bffc | ||
|
|
33b173dd55 | ||
|
|
7966d74eb3 | ||
|
|
28cb2e210d | ||
|
|
1109d412e7 | ||
|
|
3e33ece65a | ||
|
|
1dd200fba2 | ||
|
|
f0ea977f91 | ||
|
|
e830d8efc7 | ||
|
|
0b2ba2a82e | ||
|
|
df526b8f3c | ||
|
|
8fe1f32e78 | ||
|
|
c5ea93fb8e | ||
|
|
dd83bc4d26 | ||
|
|
9b4b7a1a01 | ||
|
|
548fa745d6 | ||
|
|
7d83e3f49e | ||
|
|
974284bf8a | ||
|
|
b446f00cd8 | ||
|
|
a4095b4ae5 | ||
|
|
af6dbd0dbe | ||
|
|
db17ddd069 | ||
|
|
b6b85225e7 | ||
|
|
b57abfa732 | ||
|
|
4a4927b1f3 | ||
|
|
6ac34bec97 | ||
|
|
82725e6a98 | ||
|
|
a744c8b375 | ||
|
|
5e2a34de39 | ||
|
|
8a9ab39a01 | ||
|
|
515256abfd | ||
|
|
84e5a6145e | ||
|
|
1f5618d0e3 | ||
|
|
b1de821817 | ||
|
|
f4dceb97b0 | ||
|
|
ad79f175da | ||
|
|
a4b145bc2d | ||
|
|
5491b27528 | ||
|
|
9211c7e6ab | ||
|
|
5bcb6cd116 | ||
|
|
534f2c8b07 | ||
|
|
7ad390934e | ||
|
|
2a651c38e3 | ||
|
|
ddd30f9ea0 | ||
|
|
01d41b7eea | ||
|
|
4cdea33073 | ||
|
|
33d7e10ad8 | ||
|
|
2201f9220e | ||
|
|
b8743a5ef2 | ||
|
|
b587aa8172 | ||
|
|
c37fa83675 | ||
|
|
08602afcff | ||
|
|
5b98d82f58 | ||
|
|
22cd5b0e82 | ||
|
|
93855478dd | ||
|
|
59b86d5b19 | ||
|
|
27afb9ee2e | ||
|
|
a49cd42a0c | ||
|
|
8c8442ec58 | ||
|
|
41f3173adf | ||
|
|
1a4192b40f | ||
|
|
fffda7c789 | ||
|
|
b7f6596bcb | ||
|
|
1ea49dd206 | ||
|
|
32e4f114b4 | ||
|
|
fcbedda597 | ||
|
|
2055349d76 | ||
|
|
44ee64cde5 | ||
|
|
9f916998c9 | ||
|
|
2ddef37632 | ||
|
|
538ebdc899 | ||
|
|
d6e0d661fb | ||
|
|
289deb397a | ||
|
|
68b4aab837 | ||
|
|
563293f7a9 | ||
|
|
5049694fd3 | ||
|
|
a9c8d1960c | ||
|
|
066b409a65 | ||
|
|
54ad2e0ad0 | ||
|
|
77f7bc2a7e | ||
|
|
f773d74597 | ||
|
|
90b23c4985 | ||
|
|
60b4ad410b | ||
|
|
d8391ba90c | ||
|
|
c4d41ecb00 | ||
|
|
39f5284a76 |
@@ -1,14 +1,15 @@
|
||||
[](https://waffle.io/ebean-orm/avaje-ebeanorm)
|
||||
avaje-ebeanorm
|
||||
==============
|
||||
- Release - 4.0.2 - May 19th: https://github.com/ebean-orm/avaje-ebeanorm/wiki/4.0.2-Release
|
||||
- Release - 4.0.3 and 4.0.4 contained some bug fixes plus the Model and Finder objects.
|
||||
|
||||
Main EbeanORM artifact
|
||||
|
||||
Maven Dependency
|
||||
----------------
|
||||
<dependency>
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>4.0.4</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm</artifactId>
|
||||
<version>4.0.1-RC1</version>
|
||||
<version>4.1.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>avaje-ebeanorm</name>
|
||||
@@ -94,7 +94,7 @@
|
||||
<dependency>
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm-agent</artifactId>
|
||||
<version>4.1.0</version>
|
||||
<version>4.1.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -174,23 +174,21 @@
|
||||
<!-- Enhance the meta beans -->
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm-mavenenhancer</artifactId>
|
||||
<version>4.1.0</version>
|
||||
<version>4.1.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>main</id>
|
||||
<phase>process-classes</phase>
|
||||
<configuration>
|
||||
<classSource>target/classes</classSource>
|
||||
<packages>com.avaje.ebean.meta.**</packages>
|
||||
<transformArgs>debug=1</transformArgs>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>enhance</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<!-- Not going to enhance Model bean -->
|
||||
<execution>
|
||||
<id>test</id>
|
||||
<phase>process-test-classes</phase>
|
||||
@@ -221,8 +219,29 @@
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!--
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9.1</version>
|
||||
<configuration>
|
||||
<doctitle>Ebean 4</doctitle>
|
||||
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
|
||||
<excludePackageNames>com.avaje.ebeaninternal.*:com.avaje.ebean.util</excludePackageNames>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<phase>site</phase>
|
||||
<goals>
|
||||
<goal>aggregate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
-->
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
||||
|
||||
@@ -214,14 +214,13 @@ public final class Ebean {
|
||||
* Register a server so we can get it by its name.
|
||||
*/
|
||||
private void register(EbeanServer server, boolean isPrimaryServer) {
|
||||
registerWithName(server.getName(), server, isPrimaryServer);
|
||||
}
|
||||
|
||||
private void registerWithName(String name, EbeanServer server, boolean isPrimaryServer) {
|
||||
synchronized (monitor) {
|
||||
concMap.put(server.getName(), server);
|
||||
EbeanServer existingServer = syncMap.put(server.getName(), server);
|
||||
if (existingServer != null) {
|
||||
String msg = "Existing EbeanServer [" + server.getName() + "] is being replaced?";
|
||||
logger.warn(msg);
|
||||
}
|
||||
|
||||
concMap.put(name, server);
|
||||
syncMap.put(name, server);
|
||||
if (isPrimaryServer) {
|
||||
primaryServer = server;
|
||||
}
|
||||
@@ -286,6 +285,15 @@ public final class Ebean {
|
||||
serverMgr.register(server, isPrimaryServer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Backdoor for registering a mock implementation of EbeanServer as the default server.
|
||||
*/
|
||||
protected static EbeanServer mock(String name, EbeanServer server, boolean isPrimaryServer) {
|
||||
EbeanServer originalPrimaryServer = serverMgr.primaryServer;
|
||||
serverMgr.registerWithName(name, server, isPrimaryServer);
|
||||
return originalPrimaryServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next identity value for a given bean type.
|
||||
* <p>
|
||||
|
||||
@@ -547,10 +547,32 @@ public interface EbeanServer {
|
||||
|
||||
/**
|
||||
* Find using a PagingList with explicit transaction and pageSize.
|
||||
* @deprecated
|
||||
* Deprecated in favour of findPagedList().
|
||||
* @deprecated
|
||||
*/
|
||||
public <T> PagingList<T> findPagingList(Query<T> query, Transaction transaction, int pageSize);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query.
|
||||
* <p>
|
||||
* The benefit of using this over just using the normal {@link Query#setFirstRow(int)} and
|
||||
* {@link Query#setMaxRows(int)} is that it additionally wraps an optional call to
|
||||
* {@link Query#findFutureRowCount()} to determine total row count, total page count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this works using {@link Query#setFirstRow(int)} and {@link Query#setMaxRows(int)} on
|
||||
* the query. This translates into SQL that uses limit offset, rownum or row_number
|
||||
* function to limit the result set.
|
||||
* </p>
|
||||
*
|
||||
* @param pageIndex
|
||||
* The zero based index of the page.
|
||||
* @param pageSize
|
||||
* The number of beans to return per page.
|
||||
* @return The PagedList
|
||||
*/
|
||||
public <T> PagedList<T> findPagedList(Query<T> query, Transaction transaction, int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Execute the query returning a set of entity beans.
|
||||
* <p>
|
||||
@@ -1079,4 +1101,5 @@ public interface EbeanServer {
|
||||
*/
|
||||
public JsonContext createJsonContext();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -200,10 +200,34 @@ public interface ExpressionList<T> extends Serializable {
|
||||
*
|
||||
* @param pageSize
|
||||
* the number of beans fetched per Page
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public PagingList<T> findPagingList(int pageSize);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query.
|
||||
* <p>
|
||||
* The benefit of using this over just using the normal {@link Query#setFirstRow(int)} and
|
||||
* {@link Query#setMaxRows(int)} is that it additionally wraps an optional call to
|
||||
* {@link Query#findFutureRowCount()} to determine total row count, total page count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this works using {@link Query#setFirstRow(int)} and {@link Query#setMaxRows(int)} on
|
||||
* the query. This translates into SQL that uses limit offset, rownum or row_number
|
||||
* function to limit the result set.
|
||||
* </p>
|
||||
*
|
||||
* @param pageIndex
|
||||
* The zero based index of the page.
|
||||
* @param pageSize
|
||||
* The number of beans to return per page.
|
||||
* @return The PagedList
|
||||
*/
|
||||
public PagedList<T> findPagedList(int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Add some filter predicate expressions to the many property.
|
||||
*/
|
||||
public ExpressionList<T> filterMany(String prop);
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,570 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.persistence.MappedSuperclass;
|
||||
|
||||
/**
|
||||
* A MappedSuperclass base class that provides convenience methods for inserting, updating and
|
||||
* deleting beans.
|
||||
*
|
||||
* <p>
|
||||
* By having your entity beans extend this it provides a 'Active Record' style programming model for
|
||||
* Ebean users.
|
||||
*
|
||||
* <p>
|
||||
* Note that there is a avaje-ebeanorm-mocker project that enables you to use Mockito or similar
|
||||
* tools to still mock out the underlying 'default EbeanServer' for testing purposes.
|
||||
*
|
||||
* <p>
|
||||
* You may choose not use this Model mapped superclass if you don't like the 'Active Record' style
|
||||
* or if you believe it 'pollutes' your entity beans.
|
||||
*
|
||||
* <p>
|
||||
* If you choose to use the Model mapped superclass you will probably also chose to additionally add
|
||||
* a {@link Finder} as a public static field to complete the active record pattern and provide a
|
||||
* relatively nice clean way to write queries.
|
||||
*/
|
||||
@MappedSuperclass
|
||||
public abstract class Model {
|
||||
|
||||
/**
|
||||
* Return the underlying 'default' EbeanServer.
|
||||
*
|
||||
* <p>
|
||||
* This provides full access to the API such as explicit transaction demarcation etc.
|
||||
*
|
||||
* <p>
|
||||
* Example:
|
||||
* <pre class="code">
|
||||
* Transaction transaction = Customer.db().beginTransaction();
|
||||
* try {
|
||||
*
|
||||
* // turn off cascade persist for this transaction
|
||||
* transaction.setPersistCascade(false);
|
||||
*
|
||||
* // extra control over jdbc batching for this transaction
|
||||
* transaction.setBatchGetGeneratedKeys(false);
|
||||
* transaction.setBatchMode(true);
|
||||
* transaction.setBatchSize(20);
|
||||
*
|
||||
* Customer customer = new Customer();
|
||||
* customer.setName("Roberto");
|
||||
* customer.save();
|
||||
*
|
||||
* Customer otherCustomer = new Customer();
|
||||
* otherCustomer.setName("Franko");
|
||||
* otherCustomer.save();
|
||||
*
|
||||
* transaction.commit();
|
||||
*
|
||||
* } finally {
|
||||
* transaction.end();
|
||||
* }
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public static EbeanServer db() {
|
||||
return Ebean.getServer(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a named EbeanServer that is typically different to the default server.
|
||||
*
|
||||
* <p>
|
||||
* If you are using multiple databases then each database has a name and maps to a single
|
||||
* EbeanServer. You can use this method to get an EbeanServer for another database.
|
||||
*
|
||||
* @param server
|
||||
* The name of the EbeanServer. If this is null then the default EbeanServer is returned.
|
||||
*/
|
||||
public static EbeanServer db(String server) {
|
||||
return Ebean.getServer(server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert or update this entity depending on its state.
|
||||
*
|
||||
* <p>
|
||||
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
|
||||
* insert or an update based on that.
|
||||
*/
|
||||
public void save() {
|
||||
db().save(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this entity.
|
||||
*/
|
||||
public void update() {
|
||||
db().update(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert this entity.
|
||||
*/
|
||||
public void insert() {
|
||||
db().insert(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete this entity.
|
||||
*/
|
||||
public void delete() {
|
||||
db().delete(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an update using this entity against the specified server.
|
||||
*/
|
||||
public void update(String server) {
|
||||
db(server).update(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an insert using this entity against the specified server.
|
||||
*/
|
||||
public void insert(String server) {
|
||||
db(server).insert(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a delete using this entity against the specified server.
|
||||
*/
|
||||
public void delete(String server) {
|
||||
db(server).delete(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes this entity from the database.
|
||||
*/
|
||||
public void refresh() {
|
||||
db().refresh(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper object for performing queries.
|
||||
*
|
||||
* <p>
|
||||
* Typically a Finder is defined as a public static field on an entity bean class to provide a
|
||||
* nice way to write queries.
|
||||
*
|
||||
* @param I
|
||||
* The Id type. This is most often a {@link Long} but is also often a {@link UUID} or
|
||||
* {@link String}.
|
||||
*
|
||||
* @param T
|
||||
* The bean type
|
||||
*/
|
||||
public static class Finder<I, T> {
|
||||
|
||||
private final Class<I> idType;
|
||||
private final Class<T> type;
|
||||
private final String serverName;
|
||||
|
||||
/**
|
||||
* Creates a finder for entity of type <code>T</code> with ID of type <code>I</code>.
|
||||
*
|
||||
* <p>
|
||||
* Typically you use this constructor to have a static "find" field on each entity bean.
|
||||
*/
|
||||
public Finder(Class<I> idType, Class<T> type) {
|
||||
this(null, idType, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a finder for entity of type <code>T</code> with ID of type <code>I</code>, using a
|
||||
* specific EbeanServer.
|
||||
*
|
||||
* <p>
|
||||
* Typically you don't need to use this method.
|
||||
*/
|
||||
public Finder(String serverName, Class<I> idType, Class<T> type) {
|
||||
this.type = type;
|
||||
this.idType = idType;
|
||||
this.serverName = serverName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying 'default' EbeanServer.
|
||||
*
|
||||
* <p>
|
||||
* This provides full access to the API such as explicit transaction demarcation etc.
|
||||
*
|
||||
*/
|
||||
public EbeanServer db() {
|
||||
return Ebean.getServer(serverName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return typically a different EbeanServer to the default.
|
||||
* <p>
|
||||
* This is equivilent to {@link Ebean#getServer(String)}
|
||||
*
|
||||
* @param server
|
||||
* The name of the EbeanServer. If this is null then the default EbeanServer is
|
||||
* returned.
|
||||
*/
|
||||
public EbeanServer db(String server) {
|
||||
return Ebean.getServer(server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Finder for the named EbeanServer.
|
||||
*
|
||||
* <p>
|
||||
* Create and return a new Finder for a different server.
|
||||
*/
|
||||
public Finder<I, T> on(String server) {
|
||||
return new Finder<I, T>(server, idType, type);
|
||||
}
|
||||
|
||||
// Does not exist yet but I think should
|
||||
// public void deleteById(I id) {
|
||||
// return db().deleteById(type, id);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Retrieves all entities of the given type.
|
||||
*
|
||||
* <p>
|
||||
* This is the same as (synonym for) {@link #findList()}
|
||||
*/
|
||||
public List<T> all() {
|
||||
return findList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an entity by ID.
|
||||
*
|
||||
* <p>
|
||||
* Equivilent to {@link EbeanServer#find(Class, Object)}
|
||||
*/
|
||||
public T byId(I id) {
|
||||
return db().find(type, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an entity reference for this ID.
|
||||
*
|
||||
* <p>
|
||||
* Equivilent to {@link EbeanServer#getReference(Class, Object)}
|
||||
*/
|
||||
public T ref(I id) {
|
||||
return db().getReference(type, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a filter for sorting and filtering lists of entities locally without going back to
|
||||
* the database.
|
||||
* <p>
|
||||
* Equivilent to {@link EbeanServer#filter(Class)}
|
||||
*/
|
||||
public Filter<T> filter() {
|
||||
return db().filter(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a query.
|
||||
* <p>
|
||||
* Equivilent to {@link EbeanServer#find(Class)}
|
||||
*/
|
||||
public Query<T> query() {
|
||||
return db().find(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next identity value.
|
||||
*
|
||||
* @see EbeanServer#nextId(Class)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public I nextId() {
|
||||
return (I) db().nextId(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a query and returns the results as a list of IDs.
|
||||
* <p>
|
||||
* Equivilent to {@link Query#findIds()}
|
||||
*/
|
||||
public List<Object> findIds() {
|
||||
return query().findIds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all entities of the given type.
|
||||
* <p>
|
||||
* The same as {@link #all()}
|
||||
* <p>
|
||||
* Equivilent to {@link Query#findList()}
|
||||
*/
|
||||
public List<T> findList() {
|
||||
return query().findList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the entities of the given type as a set.
|
||||
* <p>
|
||||
* Equivilent to {@link Query#findSet()}
|
||||
*/
|
||||
public Set<T> findSet() {
|
||||
return query().findSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all entities of the given type as a map of objects.
|
||||
* <p>
|
||||
* Equivilent to {@link Query#findMap()}
|
||||
*/
|
||||
public Map<?, T> findMap() {
|
||||
return query().findMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the query and returns the results as a map of the objects specifying the map key
|
||||
* property.
|
||||
* <p>
|
||||
* Equivilent to {@link Query#findMap(String, Class)}
|
||||
*/
|
||||
public <K> Map<K, T> findMap(String keyProperty, Class<K> keyType) {
|
||||
return query().findMap(keyProperty, keyType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a PagedList of all entities of the given type (use where() to specify predicates as
|
||||
* needed).
|
||||
* <p>
|
||||
* Equivilent to {@link Query#findPagedList(int, int)}
|
||||
*/
|
||||
public PagedList<T> findPagedList(int pageIndex, int pageSize) {
|
||||
return query().findPagedList(pageIndex, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a find row count query in a background thread.
|
||||
* <p>
|
||||
* Equivilent to {@link Query#findFutureRowCount()}
|
||||
*/
|
||||
public FutureRowCount<T> findFutureRowCount() {
|
||||
return query().findFutureRowCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of entities for this type. *
|
||||
* <p>
|
||||
* Equivilent to {@link Query#findRowCount()}
|
||||
*/
|
||||
public int findRowCount() {
|
||||
return query().findRowCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <code>ExpressionFactory</code> used by this query.
|
||||
*/
|
||||
public ExpressionFactory getExpressionFactory() {
|
||||
return query().getExpressionFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly sets a comma delimited list of the properties to fetch on the 'main' entity bean,
|
||||
* to load a partial object.
|
||||
* <p>
|
||||
* Equivilent to {@link Query#select(String)}
|
||||
*/
|
||||
public Query<T> select(String fetchProperties) {
|
||||
return query().select(fetchProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a path to load including all its properties.
|
||||
* <p>
|
||||
* Equivilent to {@link Query#fetch(String)}
|
||||
*/
|
||||
public Query<T> fetch(String path) {
|
||||
return query().fetch(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Additionally specifies a <code>FetchConfig</code> to specify a 'query join' and/or define the
|
||||
* lazy loading query.
|
||||
* <p>
|
||||
* Equivilent to {@link Query#fetch(String, FetchConfig)}
|
||||
*/
|
||||
public Query<T> fetch(String path, FetchConfig joinConfig) {
|
||||
return query().fetch(path, joinConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a path to fetch with a specific list properties to include, to load a partial
|
||||
* object.
|
||||
* <p>
|
||||
* Equivilent to {@link Query#fetch(String, String)}
|
||||
*/
|
||||
public Query<T> fetch(String path, String fetchProperties) {
|
||||
return query().fetch(path, fetchProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Additionally specifies a <code>FetchConfig</code> to use a separate query or lazy loading to
|
||||
* load this path.
|
||||
* <p>
|
||||
* Equivilent to {@link Query#fetch(String, String, FetchConfig)}
|
||||
*/
|
||||
public Query<T> fetch(String assocProperty, String fetchProperties, FetchConfig fetchConfig) {
|
||||
return query().fetch(assocProperty, fetchProperties, fetchConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds expressions to the <code>where</code> clause with the ability to chain on the
|
||||
* <code>ExpressionList</code>.
|
||||
* <p>
|
||||
* Equivilent to {@link Query#where()}
|
||||
*/
|
||||
public ExpressionList<T> where() {
|
||||
return query().where();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <code>order by</code> clause so that you can append an ascending or descending
|
||||
* property to the <code>order by</code> clause.
|
||||
* <p>
|
||||
* This is exactly the same as {@link #orderBy}.
|
||||
* <p>
|
||||
* Equivilent to {@link Query#order()}
|
||||
*/
|
||||
public OrderBy<T> order() {
|
||||
return query().order();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the <code>order by</code> clause, replacing the existing <code>order by</code> clause if
|
||||
* there is one.
|
||||
* <p>
|
||||
* This is exactly the same as {@link #orderBy(String)}.
|
||||
*/
|
||||
public Query<T> order(String orderByClause) {
|
||||
return query().order(orderByClause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <code>order by</code> clause so that you can append an ascending or descending
|
||||
* property to the <code>order by</code> clause.
|
||||
* <p>
|
||||
* This is exactly the same as {@link #order}.
|
||||
* <p>
|
||||
* Equivilent to {@link Query#orderBy()}
|
||||
*/
|
||||
public OrderBy<T> orderBy() {
|
||||
return query().orderBy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the <code>order by</code> clause replacing the existing <code>order by</code> clause if
|
||||
* there is one.
|
||||
* <p>
|
||||
* This is exactly the same as {@link #order(String)}.
|
||||
*/
|
||||
public Query<T> orderBy(String orderByClause) {
|
||||
return query().orderBy(orderByClause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the first row to return for this query.
|
||||
* <p>
|
||||
* Equivilent to {@link Query#setFirstRow(int)}
|
||||
*/
|
||||
public Query<T> setFirstRow(int firstRow) {
|
||||
return query().setFirstRow(firstRow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of rows to return in the query.
|
||||
* <p>
|
||||
* Equivilent to {@link Query#setMaxRows(int)}
|
||||
*/
|
||||
public Query<T> setMaxRows(int maxRows) {
|
||||
return query().setMaxRows(maxRows);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ID value to query.
|
||||
*
|
||||
* <p>
|
||||
* Use this to perform a find byId query but with additional control over the query such as
|
||||
* using select and fetch to control what parts of the object graph are returned.
|
||||
* <p>
|
||||
* Equivilent to {@link Query#setId(Object)}
|
||||
*/
|
||||
public Query<T> setId(Object id) {
|
||||
return query().setId(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a new query using the OQL.
|
||||
* <p>
|
||||
* Equivilent to {@link EbeanServer#createQuery(Class, String)}
|
||||
*/
|
||||
public Query<T> setQuery(String oql) {
|
||||
return db().createQuery(type, oql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a new query based on the <code>RawSql</code>.
|
||||
* <p>
|
||||
* Equivilent to {@link Query#setRawSql(RawSql)}
|
||||
*/
|
||||
public Query<T> setRawSql(RawSql rawSql) {
|
||||
return query().setRawSql(rawSql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query with explicit 'Autofetch' use.
|
||||
*/
|
||||
public Query<T> setAutofetch(boolean autofetch) {
|
||||
return query().setAutofetch(autofetch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query with the select with "for update" specified.
|
||||
*
|
||||
* <p>
|
||||
* This will typically create row level database locks on the selected rows.
|
||||
*/
|
||||
public Query<T> setForUpdate(boolean forUpdate) {
|
||||
return query().setForUpdate(forUpdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query specifying whether the returned beans will be read-only.
|
||||
*/
|
||||
public Query<T> setReadOnly(boolean readOnly) {
|
||||
return query().setReadOnly(readOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query specifying if the beans should be loaded into the L2 cache.
|
||||
*/
|
||||
public Query<T> setLoadBeanCache(boolean loadBeanCache) {
|
||||
return query().setLoadBeanCache(loadBeanCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query specifying if the L2 bean cache should be used.
|
||||
*/
|
||||
public Query<T> setUseCache(boolean useBeanCache) {
|
||||
return query().setUseCache(useBeanCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query specifying if the L2 query cache should be used.
|
||||
*/
|
||||
public Query<T> setUseQueryCache(boolean useQueryCache) {
|
||||
return query().setUseQueryCache(useQueryCache);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* Represents a page of results.
|
||||
* <p>
|
||||
* The benefit of using PagedList over just using the normal Query with
|
||||
* {@link Query#setFirstRow(int)} and {@link Query#setMaxRows(int)} is that it additionally wraps
|
||||
* functionality that can call {@link Query#findFutureRowCount()} to determine total row count,
|
||||
* total page count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this works using {@link Query#setFirstRow(int)} and {@link Query#setMaxRows(int)} on
|
||||
* the query. This translates into SQL that uses limit offset, rownum or row_number function to
|
||||
* limit the result set.
|
||||
* </p>
|
||||
*
|
||||
* @param <T>
|
||||
* the entity bean type
|
||||
*
|
||||
* @see Query#findPagedList(int, int)
|
||||
*/
|
||||
public interface PagedList<T> {
|
||||
|
||||
/**
|
||||
* Initiate the loading of the total row count in the background.
|
||||
*/
|
||||
public void loadRowCount();
|
||||
|
||||
/**
|
||||
* Return the Future row count. You might get this if you wish to cancel the total row count query
|
||||
* or specify a timeout for that query.
|
||||
*/
|
||||
public Future<Integer> getFutureRowCount();
|
||||
|
||||
/**
|
||||
* Return the list of entities for this page.
|
||||
*/
|
||||
public List<T> getList();
|
||||
|
||||
/**
|
||||
* Return the total row count for all pages.
|
||||
*/
|
||||
public int getTotalRowCount();
|
||||
|
||||
/**
|
||||
* Return the total number of pages based on the page size and total row count.
|
||||
*/
|
||||
public int getTotalPageCount();
|
||||
|
||||
/**
|
||||
* Return the index position of this page. Zero based.
|
||||
*/
|
||||
public int getPageIndex();
|
||||
|
||||
/**
|
||||
* Return true if there is a next page.
|
||||
*/
|
||||
public boolean hasNext();
|
||||
|
||||
/**
|
||||
* Return true if there is a previous page.
|
||||
*/
|
||||
public boolean hasPrev();
|
||||
|
||||
/**
|
||||
* Helper method to return a "X to Y of Z" string for this page where X is the first row, Y the
|
||||
* last row and Z the total row count.
|
||||
*
|
||||
* @param to
|
||||
* String to put between the first and last row
|
||||
* @param of
|
||||
* String to put between the last row and the total row count
|
||||
*
|
||||
* @return String of the format XtoYofZ.
|
||||
*/
|
||||
public String getDisplayXtoYofZ(String to, String of);
|
||||
}
|
||||
@@ -589,9 +589,7 @@ public interface Query<T> extends Serializable {
|
||||
/**
|
||||
* Execute find list query in a background thread.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a
|
||||
* timeout).
|
||||
* Deprecated with a view to simplifying internals.
|
||||
* </p>
|
||||
*
|
||||
* @return a Future object for the list result of the query
|
||||
@@ -600,25 +598,33 @@ public interface Query<T> extends Serializable {
|
||||
public FutureList<T> findFutureList();
|
||||
|
||||
/**
|
||||
* Return a PagingList for this query.
|
||||
* <p>
|
||||
* This can be used to break up a query into multiple queries to fetch the
|
||||
* data a page at a time.
|
||||
* </p>
|
||||
* <p>
|
||||
* This typically works by using a query per page and setting
|
||||
* {@link Query#setFirstRow(int)} and and {@link Query#setMaxRows(int)} on the
|
||||
* query. This usually would translate into SQL that uses limit offset, rownum
|
||||
* or row_number function to limit the result set.
|
||||
* </p>
|
||||
*
|
||||
* @param pageSize
|
||||
* the number of beans fetched per Page
|
||||
* This is being deprecated in favour of the simplier {@link Query#findPagedList(int, int)}.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public PagingList<T> findPagingList(int pageSize);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query.
|
||||
* <p>
|
||||
* The benefit of using this over just using the normal {@link Query#setFirstRow(int)} and
|
||||
* {@link Query#setMaxRows(int)} is that it additionally wraps an optional call to
|
||||
* {@link Query#findFutureRowCount()} to determine total row count, total page count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this works using {@link Query#setFirstRow(int)} and {@link Query#setMaxRows(int)} on
|
||||
* the query. This translates into SQL that uses limit offset, rownum or row_number function to
|
||||
* limit the result set.
|
||||
* </p>
|
||||
*
|
||||
* @param pageIndex
|
||||
* The zero based index of the page.
|
||||
* @param pageSize
|
||||
* The number of beans to return per page.
|
||||
* @return The PagedList
|
||||
*/
|
||||
public PagedList<T> findPagedList(int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Set a named bind parameter. Named parameters have a colon to prefix the
|
||||
* name.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
@@ -135,18 +136,33 @@ import com.avaje.ebean.util.CamelCaseHelper;
|
||||
* Note that lazy loading also works with object graphs built with RawSql.
|
||||
* </p>
|
||||
*
|
||||
* @author rbygrave
|
||||
*
|
||||
*/
|
||||
public final class RawSql implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final ResultSet resultSet;
|
||||
|
||||
private final Sql sql;
|
||||
|
||||
private final ColumnMapping columnMapping;
|
||||
|
||||
protected RawSql(Sql sql, ColumnMapping columnMapping) {
|
||||
/**
|
||||
* Construct with a ResultSet and properties that the columns map to.
|
||||
* <p>
|
||||
* The properties listed in the propertyNames must be in the same order as the columns in the
|
||||
* resultSet.
|
||||
* <p>
|
||||
* When a query executes this RawSql object then it will close the resultSet.
|
||||
*/
|
||||
public RawSql(ResultSet resultSet, String... propertyNames) {
|
||||
this.resultSet = resultSet;
|
||||
this.sql = null;
|
||||
this.columnMapping = new ColumnMapping(propertyNames);
|
||||
}
|
||||
|
||||
protected RawSql(ResultSet resultSet, Sql sql, ColumnMapping columnMapping) {
|
||||
this.resultSet = resultSet;
|
||||
this.sql = sql;
|
||||
this.columnMapping = columnMapping;
|
||||
}
|
||||
@@ -158,6 +174,14 @@ public final class RawSql implements Serializable {
|
||||
return sql;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the resultSet if this is a ResultSet based RawSql.
|
||||
*/
|
||||
public ResultSet getResultSet() {
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column mapping for the SQL columns to bean properties.
|
||||
*/
|
||||
@@ -169,6 +193,9 @@ public final class RawSql implements Serializable {
|
||||
* Return the hash for this query.
|
||||
*/
|
||||
public int queryHash() {
|
||||
if (resultSet != null) {
|
||||
return 31 * columnMapping.queryHash();
|
||||
}
|
||||
return 31 * sql.queryHash() + columnMapping.queryHash();
|
||||
}
|
||||
|
||||
@@ -329,6 +356,7 @@ public final class RawSql implements Serializable {
|
||||
private final LinkedHashMap<String, Column> dbColumnMap;
|
||||
|
||||
private final Map<String, String> propertyMap;
|
||||
|
||||
private final Map<String, Column> propertyColumnMap;
|
||||
|
||||
private final boolean parsed;
|
||||
@@ -364,6 +392,26 @@ public final class RawSql implements Serializable {
|
||||
this.propertyColumnMap = null;
|
||||
this.dbColumnMap = new LinkedHashMap<String, Column>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for ResultSet use.
|
||||
*/
|
||||
protected ColumnMapping(String... propertyNames) {
|
||||
this.immutable = false;
|
||||
this.parsed = false;
|
||||
this.propertyMap = null;
|
||||
//this.propertyColumnMap = null;
|
||||
this.dbColumnMap = new LinkedHashMap<String, Column>();
|
||||
|
||||
int hc = 31;
|
||||
int pos = 0;
|
||||
for (String prop : propertyNames) {
|
||||
hc = 31 * hc + prop.hashCode();
|
||||
dbColumnMap.put(prop, new Column(pos++, prop, null, prop));
|
||||
}
|
||||
propertyColumnMap = dbColumnMap;
|
||||
this.queryHashCode = hc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an immutable ColumnMapping based on collected information.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
|
||||
import com.avaje.ebean.RawSql.ColumnMapping;
|
||||
import com.avaje.ebean.RawSql.Sql;
|
||||
|
||||
@@ -10,8 +12,6 @@ import com.avaje.ebean.RawSql.Sql;
|
||||
* named query.
|
||||
* </p>
|
||||
*
|
||||
* @author rbygrave
|
||||
*
|
||||
* @see RawSql
|
||||
*/
|
||||
public class RawSqlBuilder {
|
||||
@@ -21,10 +21,23 @@ public class RawSqlBuilder {
|
||||
*/
|
||||
public static final String IGNORE_COLUMN = "$$_IGNORE_COLUMN_$$";
|
||||
|
||||
private final ResultSet resultSet;
|
||||
|
||||
private final Sql sql;
|
||||
|
||||
private final ColumnMapping columnMapping;
|
||||
|
||||
/**
|
||||
* Create and return a RawSql object based on the resultSet and list of properties the columns in
|
||||
* the resultSet map to.
|
||||
* <p>
|
||||
* The properties listed in the propertyNames must be in the same order as the columns in the
|
||||
* resultSet.
|
||||
*/
|
||||
public static RawSql resultSet(ResultSet resultSet, String... propertyNames) {
|
||||
return new RawSql(resultSet, propertyNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an unparsed RawSqlBuilder. Unlike a parsed one this query can not be
|
||||
* modified - so no additional WHERE or HAVING expressions can be added to
|
||||
@@ -58,9 +71,17 @@ public class RawSqlBuilder {
|
||||
return new RawSqlBuilder(sql2, mapping);
|
||||
}
|
||||
|
||||
|
||||
private RawSqlBuilder(ResultSet resultSet, ColumnMapping columnMapping) {
|
||||
this.resultSet = resultSet;
|
||||
this.columnMapping = columnMapping;
|
||||
this.sql = null;
|
||||
}
|
||||
|
||||
private RawSqlBuilder(Sql sql, ColumnMapping columnMapping) {
|
||||
this.sql = sql;
|
||||
this.columnMapping = columnMapping;
|
||||
this.resultSet = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,7 +113,7 @@ public class RawSqlBuilder {
|
||||
* has been defined.
|
||||
*/
|
||||
public RawSql create() {
|
||||
return new RawSql(sql, columnMapping.createImmutableCopy());
|
||||
return new RawSql(resultSet, sql, columnMapping.createImmutableCopy());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,4 +122,6 @@ public class RawSqlBuilder {
|
||||
protected Sql getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface ColumnHstore {
|
||||
|
||||
}
|
||||
@@ -137,22 +137,6 @@ public interface BeanCollection<E> extends Serializable {
|
||||
*/
|
||||
public Collection<?> getActualEntries();
|
||||
|
||||
/**
|
||||
* Set to true if maxRows was hit and there are actually more rows available.
|
||||
* <p>
|
||||
* Can be used by client code that is paging through results using
|
||||
* setFirstRow() setMaxRows(). If this returns true then the client can
|
||||
* display a 'next' button etc.
|
||||
* </p>
|
||||
*/
|
||||
public boolean hasMoreRows();
|
||||
|
||||
/**
|
||||
* Set to true when maxRows is hit but there are actually more rows available.
|
||||
* This is set so that client code knows that there is more data available.
|
||||
*/
|
||||
public void setHasMoreRows(boolean hasMoreRows);
|
||||
|
||||
/**
|
||||
* return true if there are real rows held. Return false is this is using
|
||||
* Deferred fetch to lazy load the rows and the rows have not yet been
|
||||
|
||||
@@ -182,6 +182,13 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
public Object getEmbeddedOwner() {
|
||||
return embeddedOwner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property index (for the parent) of this embedded bean.
|
||||
*/
|
||||
public int getEmbeddedOwnerIndex() {
|
||||
return embeddedOwnerIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Special case for a OneToOne, Set the parent bean (by relationship). This is
|
||||
@@ -444,6 +451,9 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property name for the given property.
|
||||
*/
|
||||
public String getProperty(int propertyIndex) {
|
||||
if (propertyIndex == -1) {
|
||||
return null;
|
||||
@@ -451,18 +461,38 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
return owner._ebean_getPropertyName(propertyIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of properties.s
|
||||
*/
|
||||
public int getPropertyLength() {
|
||||
return owner._ebean_getPropertyNames().length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the property to be treated as unloaded. Used for properties initialised in default
|
||||
* constructor.
|
||||
*/
|
||||
public void setPropertyUnloaded(int propertyIndex) {
|
||||
loadedProps[propertyIndex] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the property to be loaded.
|
||||
*/
|
||||
public void setLoadedProperty(int propertyIndex) {
|
||||
loadedProps[propertyIndex] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the property is loaded.
|
||||
*/
|
||||
public boolean isLoadedProperty(int propertyIndex) {
|
||||
return loadedProps[propertyIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the property is considered changed.
|
||||
*/
|
||||
public boolean isChangedProperty(int propertyIndex) {
|
||||
return (changedProps != null && changedProps[propertyIndex]);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.avaje.ebean.bean;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Identifies a unique node of an object graph.
|
||||
@@ -70,7 +69,7 @@ public final class ObjectGraphNode implements Serializable {
|
||||
|
||||
public int hashCode() {
|
||||
int hc = 31 * originQueryPoint.hashCode();
|
||||
hc = 31 * hc + Objects.hashCode(path);
|
||||
hc = 31 * hc + (path == null ? 0 : path.hashCode());
|
||||
return hc;
|
||||
}
|
||||
|
||||
@@ -83,7 +82,7 @@ public final class ObjectGraphNode implements Serializable {
|
||||
}
|
||||
|
||||
ObjectGraphNode e = (ObjectGraphNode) obj;
|
||||
return Objects.equals(e.path, path)
|
||||
return ((e.path == path) || (e.path != null && e.path.equals(path)))
|
||||
&& e.originQueryPoint.equals(originQueryPoint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,12 +45,6 @@ public abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
*/
|
||||
protected final String propertyName;
|
||||
|
||||
/**
|
||||
* Flag set to true if rows are limited by firstRow maxRows and more rows
|
||||
* exist. For use by client to enable 'next' for paging.
|
||||
*/
|
||||
protected boolean hasMoreRows;
|
||||
|
||||
protected ModifyHolder<E> modifyHolder;
|
||||
|
||||
protected ModifyListenMode modifyListenMode;
|
||||
@@ -151,26 +145,6 @@ public abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if maxRows was hit and there are actually more rows available.
|
||||
* <p>
|
||||
* Can be used by client code that is paging through results using
|
||||
* setFirstRow() setMaxRows(). If this returns true then the client can
|
||||
* display a 'next' button etc.
|
||||
* </p>
|
||||
*/
|
||||
public boolean hasMoreRows() {
|
||||
return hasMoreRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true when maxRows is hit but there are actually more rows available.
|
||||
* This is set so that client code knows that there is more data available.
|
||||
*/
|
||||
public void setHasMoreRows(boolean hasMoreRows) {
|
||||
this.hasMoreRows = hasMoreRows;
|
||||
}
|
||||
|
||||
protected void checkReadOnly() {
|
||||
if (readOnly) {
|
||||
String msg = "This collection is in ReadOnly mode";
|
||||
|
||||
@@ -153,7 +153,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
StringBuffer sb = new StringBuffer(50);
|
||||
sb.append("BeanList ");
|
||||
if (isReadOnly()) {
|
||||
sb.append("readOnly ");
|
||||
@@ -163,7 +163,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
|
||||
} else {
|
||||
sb.append("size[").append(list.size()).append("] ");
|
||||
sb.append("hasMoreRows[").append(hasMoreRows).append("] ");
|
||||
sb.append("list").append(list).append("");
|
||||
}
|
||||
return sb.toString();
|
||||
|
||||
@@ -154,7 +154,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
StringBuffer sb = new StringBuffer(50);
|
||||
sb.append("BeanMap ");
|
||||
if (isReadOnly()) {
|
||||
sb.append("readOnly ");
|
||||
@@ -164,7 +164,6 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
|
||||
} else {
|
||||
sb.append("size[").append(map.size()).append("]");
|
||||
sb.append(" hasMoreRows[").append(hasMoreRows).append("]");
|
||||
sb.append(" map").append(map);
|
||||
}
|
||||
return sb.toString();
|
||||
|
||||
@@ -143,7 +143,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
StringBuffer sb = new StringBuffer(50);
|
||||
sb.append("BeanSet ");
|
||||
if (isReadOnly()) {
|
||||
sb.append("readOnly ");
|
||||
@@ -153,7 +153,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
|
||||
} else {
|
||||
sb.append("size[").append(set.size()).append("]");
|
||||
sb.append(" hasMoreRows[").append(hasMoreRows).append("]");
|
||||
sb.append(" set").append(set);
|
||||
}
|
||||
return sb.toString();
|
||||
|
||||
@@ -204,6 +204,12 @@ public class ServerConfig {
|
||||
* Default behaviour for updates when cascade save on a O2M or M2M to delete any missing children.
|
||||
*/
|
||||
private boolean updatesDeleteMissingChildren = true;
|
||||
|
||||
/**
|
||||
* Setting to indicate if UUID should be stored as binary(16) or varchar(40).
|
||||
*/
|
||||
private boolean uuidStoreAsBinary;
|
||||
|
||||
|
||||
private List<BeanPersistController> persistControllers = new ArrayList<BeanPersistController>();
|
||||
private List<BeanPersistListener<?>> persistListeners = new ArrayList<BeanPersistListener<?>>();
|
||||
@@ -796,6 +802,21 @@ public class ServerConfig {
|
||||
this.dbEncrypt = dbEncrypt;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if UUID should be stored as binary(16) (as opposed to varchar(40)).
|
||||
*/
|
||||
public boolean isUuidStoreAsBinary() {
|
||||
return uuidStoreAsBinary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if UUID should be stored as binary(16) (as opposed to varchar(40)).
|
||||
*/
|
||||
public void setUuidStoreAsBinary(boolean uuidStoreAsBinary) {
|
||||
this.uuidStoreAsBinary = uuidStoreAsBinary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to run the DDL generation on startup.
|
||||
*/
|
||||
@@ -1282,7 +1303,7 @@ public class ServerConfig {
|
||||
|
||||
collectQueryStatsByNode = p.getBoolean("collectQueryStatsByNode", true);
|
||||
collectQueryOrigins = p.getBoolean("collectQueryOrigins", true);
|
||||
|
||||
|
||||
updateChangesOnly = p.getBoolean("updateChangesOnly", true);
|
||||
|
||||
boolean defaultDeleteMissingChildren = p.getBoolean("defaultDeleteMissingChildren", true);
|
||||
@@ -1299,6 +1320,7 @@ public class ServerConfig {
|
||||
databaseBooleanTrue = p.get("databaseBooleanTrue", null);
|
||||
databaseBooleanFalse = p.get("databaseBooleanFalse", null);
|
||||
databasePlatformName = p.get("databasePlatformName", null);
|
||||
uuidStoreAsBinary = p.getBoolean("uuidStoreAsBinary", false);
|
||||
|
||||
lazyLoadBatchSize = p.getInt("lazyLoadBatchSize", 1);
|
||||
queryBatchSize = p.getInt("queryBatchSize", DEFAULT_QUERY_BATCH_SIZE);
|
||||
|
||||
@@ -57,6 +57,13 @@ public final class TableName {
|
||||
this.name = split[len - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a qualifiedTableName that might include a catalog and schema and just return the table name.
|
||||
*/
|
||||
public static String parse(String qualifiedTableName) {
|
||||
return new TableName(qualifiedTableName).getName();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getQualifiedName();
|
||||
}
|
||||
@@ -121,6 +128,18 @@ public final class TableName {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a catalog and schema prefix if they exist to the string builder.
|
||||
*/
|
||||
public void appendCatalogAndSchema(StringBuilder buffer) {
|
||||
if (catalog != null) {
|
||||
buffer.append(catalog).append(".");
|
||||
}
|
||||
if (schema != null) {
|
||||
buffer.append(schema).append(".");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if is table name is valid i.e. it has at least a name.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.config.TableName;
|
||||
|
||||
/**
|
||||
* Used to support DB specific syntax for DDL generation.
|
||||
*/
|
||||
@@ -43,6 +45,17 @@ public class DbDdlSyntax {
|
||||
return pk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column definition for an identity column.
|
||||
*/
|
||||
public String getIdentityColumnDefn(String columnDefn) {
|
||||
String identity = getIdentity();
|
||||
if (identity != null && identity.length() > 0) {
|
||||
return columnDefn+" "+identity;
|
||||
}
|
||||
return columnDefn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the identity clause for DB's that have identities.
|
||||
*/
|
||||
@@ -248,26 +261,29 @@ public class DbDdlSyntax {
|
||||
this.inlinePrimaryKeyConstraint = inlinePrimaryKeyConstraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns a fully index name.
|
||||
*/
|
||||
public String getIndexName(String table, String propName, int ixCount) {
|
||||
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
buffer.append("ix_");
|
||||
buffer.append(table);
|
||||
buffer.append("_");
|
||||
buffer.append(propName);
|
||||
|
||||
StringBuilder buffer = new StringBuilder(30);
|
||||
buffer.append("ix_").append(TableName.parse(table));
|
||||
buffer.append("_").append(propName);
|
||||
|
||||
addSuffix(buffer, ixCount);
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns a fully qualified foreign key constraint name.
|
||||
*/
|
||||
public String getForeignKeyName(String table, String propName, int fkCount) {
|
||||
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
buffer.append("fk_");
|
||||
buffer.append(table);
|
||||
buffer.append("_");
|
||||
buffer.append(propName);
|
||||
StringBuilder buffer = new StringBuilder(30);
|
||||
buffer.append("fk_").append(TableName.parse(table));
|
||||
buffer.append("_").append(propName);
|
||||
|
||||
addSuffix(buffer, fkCount);
|
||||
|
||||
@@ -276,15 +292,10 @@ public class DbDdlSyntax {
|
||||
|
||||
/**
|
||||
* Adds the suffix.
|
||||
*
|
||||
* @param buffer
|
||||
* the buffer
|
||||
* @param count
|
||||
* the count
|
||||
*/
|
||||
protected void addSuffix(StringBuilder buffer, int count) {
|
||||
final String suffixNr = Integer.toString(count);
|
||||
final int suffixLen = suffixNr.length() + 1;
|
||||
String suffixNr = Integer.toString(count);
|
||||
int suffixLen = suffixNr.length() + 1;
|
||||
|
||||
if (buffer.length() + suffixLen > maxConstraintNameLength) {
|
||||
buffer.setLength(maxConstraintNameLength - suffixLen);
|
||||
|
||||
@@ -17,22 +17,21 @@ public class LimitOffsetSqlLimiter implements SqlLimiter {
|
||||
|
||||
public SqlLimitResponse limit(SqlLimitRequest request) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(512);
|
||||
String dbSql = request.getDbSql();
|
||||
|
||||
StringBuilder sb = new StringBuilder(50 + dbSql.length());
|
||||
sb.append("select ");
|
||||
if (request.isDistinct()) {
|
||||
sb.append("distinct ");
|
||||
}
|
||||
|
||||
sb.append(request.getDbSql());
|
||||
sb.append(dbSql);
|
||||
|
||||
int firstRow = request.getFirstRow();
|
||||
int maxRows = request.getMaxRows();
|
||||
if (maxRows > 0) {
|
||||
maxRows = maxRows + 1;
|
||||
}
|
||||
|
||||
if (maxRows > 0 || firstRow > 0) {
|
||||
sb.append(" ").append(NEW_LINE).append(LIMIT).append(" ").append(maxRows);
|
||||
sb.append(" ").append(LIMIT).append(" ").append(maxRows);
|
||||
if (firstRow > 0) {
|
||||
sb.append(" ").append(OFFSET).append(" ");
|
||||
sb.append(firstRow);
|
||||
|
||||
@@ -26,9 +26,7 @@ public class MsSqlServer2005SqlLimiter implements SqlLimiter {
|
||||
|
||||
int lastRow = request.getMaxRows();
|
||||
if (lastRow > 0) {
|
||||
// fetch 1 more than we return so that
|
||||
// we know if more rows are available
|
||||
lastRow = lastRow + firstRow + 1;
|
||||
lastRow = lastRow + firstRow;
|
||||
}
|
||||
|
||||
if (firstRow < 1) {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.config.GlobalProperties;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Postgres v8.3 specific platform.
|
||||
* <p>
|
||||
* No support for getGeneratedKeys.
|
||||
* </p>
|
||||
*/
|
||||
public class Postgres8Platform extends DatabasePlatform {
|
||||
|
||||
public Postgres8Platform() {
|
||||
super();
|
||||
this.name = "postgres";
|
||||
this.selectCountWithAlias = true;
|
||||
this.blobDbType = Types.LONGVARBINARY;
|
||||
this.clobDbType = Types.VARCHAR;
|
||||
|
||||
this.dbEncrypt = new PostgresDbEncrypt();
|
||||
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(false);
|
||||
this.dbIdentity.setIdType(IdType.SEQUENCE);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
|
||||
String colAlias = GlobalProperties.get("ebean.columnAliasPrefix", null);
|
||||
if (colAlias == null) {
|
||||
// Postgres requires the "as" keyword for column alias
|
||||
GlobalProperties.put("ebean.columnAliasPrefix", "as c");
|
||||
}
|
||||
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
|
||||
// dbTypeMap.put(Types.BOOLEAN, new DbType("bit default 0"));
|
||||
|
||||
dbTypeMap.put(Types.INTEGER, new DbType("integer", false));
|
||||
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.BINARY, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.VARBINARY, new DbType("bytea", false));
|
||||
|
||||
dbTypeMap.put(Types.BLOB, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.CLOB, new DbType("text"));
|
||||
dbTypeMap.put(Types.LONGVARBINARY, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.LONGVARCHAR, new DbType("text"));
|
||||
|
||||
dbDdlSyntax.setDropTableCascade("cascade");
|
||||
dbDdlSyntax.setDropIfExists("if exists");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Postgres specific sequence IdGenerator.
|
||||
*/
|
||||
@Override
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
|
||||
String seqName, int batchSize) {
|
||||
|
||||
return new PostgresSequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String withForUpdate(String sql) {
|
||||
return sql + " for update";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
public class PostgresDdlSyntax extends DbDdlSyntax {
|
||||
|
||||
/**
|
||||
* Map bigint, integer and smallint into their equivilent serial types.
|
||||
*/
|
||||
@Override
|
||||
public String getIdentityColumnDefn(String columnDefn) {
|
||||
|
||||
//smallserial, serial and bigserial
|
||||
if ("bigint".equalsIgnoreCase(columnDefn)) {
|
||||
return "bigserial";
|
||||
}
|
||||
if ("integer".equalsIgnoreCase(columnDefn)) {
|
||||
return "serial";
|
||||
}
|
||||
if ("smallint".equalsIgnoreCase(columnDefn)) {
|
||||
return "smallserial";
|
||||
}
|
||||
|
||||
return columnDefn;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,27 +4,37 @@ import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.config.GlobalProperties;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Postgres v8.3 specific platform.
|
||||
* Postgres v9 specific platform.
|
||||
* <p>
|
||||
* No support for getGeneratedKeys.
|
||||
* Uses serial types and getGeneratedKeys.
|
||||
* </p>
|
||||
*/
|
||||
public class PostgresPlatform extends DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Unique jdbc type id defined for hstore type.
|
||||
*/
|
||||
public static final int TYPE_HSTORE = 4001;
|
||||
|
||||
public PostgresPlatform() {
|
||||
super();
|
||||
this.name = "postgres";
|
||||
|
||||
this.dbDdlSyntax = new PostgresDdlSyntax();
|
||||
|
||||
this.selectCountWithAlias = true;
|
||||
this.blobDbType = Types.LONGVARBINARY;
|
||||
this.clobDbType = Types.VARCHAR;
|
||||
|
||||
this.dbEncrypt = new PostgresDbEncrypt();
|
||||
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(false);
|
||||
this.dbIdentity.setIdType(IdType.SEQUENCE);
|
||||
// Use Identity and getGeneratedKeys
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
|
||||
String colAlias = GlobalProperties.get("ebean.columnAliasPrefix", null);
|
||||
@@ -36,8 +46,8 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
|
||||
// dbTypeMap.put(Types.BOOLEAN, new DbType("bit default 0"));
|
||||
|
||||
dbTypeMap.put(TYPE_HSTORE, new DbType("hstore"));
|
||||
|
||||
dbTypeMap.put(Types.INTEGER, new DbType("integer", false));
|
||||
dbTypeMap.put(Types.DOUBLE, new DbType("float"));
|
||||
dbTypeMap.put(Types.TINYINT, new DbType("smallint"));
|
||||
|
||||
@@ -15,7 +15,7 @@ public class RowNumberSqlLimiter implements SqlLimiter {
|
||||
*/
|
||||
private static final String ROW_NUMBER_AS = ") as rn, ";
|
||||
|
||||
final String rowNumberWindowAlias;
|
||||
private final String rowNumberWindowAlias;
|
||||
|
||||
/**
|
||||
* Specify the name of the rowNumberWindowAlias.
|
||||
@@ -30,16 +30,18 @@ public class RowNumberSqlLimiter implements SqlLimiter {
|
||||
|
||||
public SqlLimitResponse limit(SqlLimitRequest request) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(500);
|
||||
String dbSql = request.getDbSql();
|
||||
|
||||
StringBuilder sb = new StringBuilder(60 + dbSql.length());
|
||||
|
||||
int firstRow = request.getFirstRow();
|
||||
|
||||
int lastRow = request.getMaxRows();
|
||||
if (lastRow > 0) {
|
||||
lastRow = lastRow + firstRow + 1;
|
||||
lastRow = lastRow + firstRow;
|
||||
}
|
||||
|
||||
sb.append("select * from (").append(NEW_LINE);
|
||||
sb.append("select * from ( ");
|
||||
|
||||
sb.append("select ");
|
||||
if (request.isDistinct()) {
|
||||
@@ -50,9 +52,9 @@ public class RowNumberSqlLimiter implements SqlLimiter {
|
||||
sb.append(request.getDbOrderBy());
|
||||
sb.append(ROW_NUMBER_AS);
|
||||
|
||||
sb.append(request.getDbSql());
|
||||
sb.append(dbSql);
|
||||
|
||||
sb.append(NEW_LINE).append(") ");
|
||||
sb.append(" ) ");
|
||||
sb.append(rowNumberWindowAlias);
|
||||
sb.append(" where ");
|
||||
if (firstRow > 0) {
|
||||
|
||||
@@ -35,38 +35,40 @@ public class RownumSqlLimiter implements SqlLimiter {
|
||||
// :MAX_ROW_TO_FETCH )
|
||||
// where rnum >= :MIN_ROW_TO_FETCH;
|
||||
|
||||
StringBuilder sb = new StringBuilder(500);
|
||||
String dbSql = request.getDbSql();
|
||||
|
||||
StringBuilder sb = new StringBuilder(60 + dbSql.length());
|
||||
|
||||
int firstRow = request.getFirstRow();
|
||||
|
||||
int lastRow = request.getMaxRows();
|
||||
if (lastRow > 0) {
|
||||
lastRow = lastRow + firstRow + 1;
|
||||
lastRow = lastRow + firstRow;
|
||||
}
|
||||
|
||||
sb.append("select * ").append(NEW_LINE).append("from ( ");
|
||||
sb.append("select * from ( ");
|
||||
|
||||
sb.append("select ");
|
||||
if (useFirstRowsHint && request.getMaxRows() > 0) {
|
||||
sb.append("/*+ FIRST_ROWS(").append(request.getMaxRows() + 1).append(") */ ");
|
||||
sb.append("/*+ FIRST_ROWS(").append(request.getMaxRows()).append(") */ ");
|
||||
}
|
||||
|
||||
sb.append("rownum ").append(rnum).append(", a.* ").append(NEW_LINE);
|
||||
sb.append(" from (");//
|
||||
sb.append("rownum ").append(rnum).append(", a.* ");
|
||||
sb.append(" from (");
|
||||
|
||||
sb.append(" select ");
|
||||
if (request.isDistinct()) {
|
||||
sb.append("distinct ");
|
||||
}
|
||||
sb.append(request.getDbSql());
|
||||
sb.append(dbSql);
|
||||
|
||||
sb.append(NEW_LINE).append(" ) a ");
|
||||
sb.append(NEW_LINE).append(" ) a ");
|
||||
if (lastRow > 0) {
|
||||
sb.append(NEW_LINE).append(" where rownum <= ").append(lastRow);
|
||||
sb.append(" where rownum <= ").append(lastRow);
|
||||
}
|
||||
sb.append(NEW_LINE).append(" ) ");
|
||||
sb.append(" ) ");
|
||||
if (firstRow > 0) {
|
||||
sb.append(NEW_LINE).append("where ");
|
||||
sb.append(" where ");
|
||||
sb.append(rnum).append(" > ").append(firstRow);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,15 +8,12 @@ public class SqlAnywhereLimiter implements SqlLimiter {
|
||||
|
||||
public SqlLimitResponse limit(SqlLimitRequest request) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(500);
|
||||
String dbSql = request.getDbSql();
|
||||
|
||||
StringBuilder sb = new StringBuilder(60 + dbSql.length());
|
||||
|
||||
int firstRow = request.getFirstRow();
|
||||
int maxRows = request.getMaxRows();
|
||||
if (maxRows > 0) {
|
||||
// fetch 1 more than we return so that
|
||||
// we know if more rows are available
|
||||
maxRows = maxRows + 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* SELECT TOP xx START AT xx ... FROM ...
|
||||
@@ -31,7 +28,7 @@ public class SqlAnywhereLimiter implements SqlLimiter {
|
||||
if (firstRow > 0) {
|
||||
sb.append("start at ").append(firstRow + 1).append(" ");
|
||||
}
|
||||
sb.append(request.getDbSql());
|
||||
sb.append(dbSql);
|
||||
|
||||
String sql = request.getDbPlatform().completeSql(sb.toString(), request.getOrmQuery());
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ import com.avaje.ebean.Query;
|
||||
/**
|
||||
* The request object for the query that can have sql limiting applied to it
|
||||
* (such as a LIMIT OFFSET clause).
|
||||
*
|
||||
* @author rob
|
||||
*/
|
||||
public interface SqlLimitRequest {
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import java.util.Set;
|
||||
/**
|
||||
* Used to enhance or override the default bean persistence mechanism.
|
||||
* <p>
|
||||
* Note that if want to totally change the finding, you need to use a BeanFinder
|
||||
* Note that if want to totally change the finding, you need to use a BeanQueryAdapter
|
||||
* rather than using postLoad().
|
||||
* </p>
|
||||
* <p>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.avaje.ebeaninternal.api;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A hash for a query plan.
|
||||
*/
|
||||
@@ -26,7 +24,7 @@ public class HashQueryPlan {
|
||||
public int hashCode() {
|
||||
int hc = planHash;
|
||||
hc = hc * 31 + bindCount;
|
||||
hc = hc * 31 + Objects.hashCode(rawSql);
|
||||
hc = hc * 31 + (rawSql == null ? 0 : rawSql.hashCode());
|
||||
return hc;
|
||||
}
|
||||
|
||||
@@ -41,6 +39,6 @@ public class HashQueryPlan {
|
||||
HashQueryPlan e = (HashQueryPlan) obj;
|
||||
return e.planHash == planHash
|
||||
&& e.bindCount == bindCount
|
||||
&& Objects.equals(e.rawSql, rawSql);
|
||||
&& ((e.rawSql == rawSql) || (e.rawSql != null && e.rawSql.equals(rawSql)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.avaje.ebeaninternal.api;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Used to build HashQueryPlan instances.
|
||||
*/
|
||||
@@ -33,7 +31,7 @@ public class HashQueryPlanBuilder {
|
||||
* Add an object to the hash calculation.
|
||||
*/
|
||||
public HashQueryPlanBuilder add(Object object) {
|
||||
planHash = planHash * 31 + Objects.hashCode(object);
|
||||
planHash = planHash * 31 + (object == null ? 0 : object.hashCode());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ package com.avaje.ebeaninternal.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.avaje.ebean.Transaction;
|
||||
import com.avaje.ebean.bean.EntityBeanIntercept;
|
||||
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
|
||||
|
||||
/**
|
||||
* Request for loading ManyToOne and OneToOne relationships.
|
||||
@@ -18,10 +18,12 @@ public class LoadBeanRequest extends LoadRequest {
|
||||
|
||||
private final boolean loadCache;
|
||||
|
||||
public LoadBeanRequest(LoadBeanBuffer LoadBuffer, Transaction transaction, boolean lazy, String lazyLoadProperty,
|
||||
boolean loadCache) {
|
||||
|
||||
super(transaction, lazy);
|
||||
public LoadBeanRequest(LoadBeanBuffer LoadBuffer, boolean lazy, String lazyLoadProperty, boolean loadCache) {
|
||||
this(LoadBuffer, null, lazy, lazyLoadProperty, loadCache);
|
||||
}
|
||||
|
||||
public LoadBeanRequest(LoadBeanBuffer LoadBuffer, OrmQueryRequest<?> parentRequest, boolean lazy, String lazyLoadProperty, boolean loadCache) {
|
||||
super(parentRequest, lazy);
|
||||
this.LoadBuffer = LoadBuffer;
|
||||
this.batch = LoadBuffer.getBatch();
|
||||
this.lazyLoadProperty = lazyLoadProperty;
|
||||
|
||||
@@ -2,69 +2,71 @@ package com.avaje.ebeaninternal.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.avaje.ebean.Transaction;
|
||||
import com.avaje.ebean.bean.BeanCollection;
|
||||
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
|
||||
|
||||
/**
|
||||
* Request for loading Associated One Beans.
|
||||
* Request for loading Associated Many Beans.
|
||||
*/
|
||||
public class LoadManyRequest extends LoadRequest {
|
||||
|
||||
private final List<BeanCollection<?>> batch;
|
||||
|
||||
private final List<BeanCollection<?>> batch;
|
||||
private final LoadManyBuffer loadContext;
|
||||
|
||||
private final LoadManyBuffer loadContext;
|
||||
private final boolean onlyIds;
|
||||
|
||||
private final boolean onlyIds;
|
||||
|
||||
private final boolean loadCache;
|
||||
|
||||
public LoadManyRequest(LoadManyBuffer loadContext, Transaction transaction, int batchSize, boolean lazy,
|
||||
private final boolean loadCache;
|
||||
|
||||
public LoadManyRequest(LoadManyBuffer loadContext, int batchSize, boolean lazy,boolean onlyIds, boolean loadCache) {
|
||||
this(loadContext, null, batchSize, lazy, onlyIds, loadCache);
|
||||
}
|
||||
|
||||
public LoadManyRequest(LoadManyBuffer loadContext, OrmQueryRequest<?> parentRequest, int batchSize, boolean lazy,
|
||||
boolean onlyIds, boolean loadCache) {
|
||||
|
||||
super(transaction, lazy);
|
||||
this.loadContext = loadContext;
|
||||
this.batch = loadContext.getBatch();
|
||||
this.onlyIds = onlyIds;
|
||||
this.loadCache = loadCache;
|
||||
}
|
||||
super(parentRequest, lazy);
|
||||
this.loadContext = loadContext;
|
||||
this.batch = loadContext.getBatch();
|
||||
this.onlyIds = onlyIds;
|
||||
this.loadCache = loadCache;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return "path:" + loadContext.getFullPath() + " size:"+ batch.size();
|
||||
}
|
||||
public String getDescription() {
|
||||
return "path:" + loadContext.getFullPath() + " size:" + batch.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the batch of collections to actually load.
|
||||
*/
|
||||
public List<BeanCollection<?>> getBatch() {
|
||||
return batch;
|
||||
}
|
||||
/**
|
||||
* Return the batch of collections to actually load.
|
||||
*/
|
||||
public List<BeanCollection<?>> getBatch() {
|
||||
return batch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the load context.
|
||||
*/
|
||||
public LoadManyBuffer getLoadContext() {
|
||||
return loadContext;
|
||||
}
|
||||
/**
|
||||
* Return the load context.
|
||||
*/
|
||||
public LoadManyBuffer getLoadContext() {
|
||||
return loadContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if lazy loading should only load the id values.
|
||||
* <p>
|
||||
* This for use when lazy loading is invoked on methods such
|
||||
* as clear() and removeAll() where it generally makes sense to
|
||||
* only fetch the Id values as the other property information is
|
||||
* not used.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isOnlyIds() {
|
||||
return onlyIds;
|
||||
}
|
||||
/**
|
||||
* Return true if lazy loading should only load the id values.
|
||||
* <p>
|
||||
* This for use when lazy loading is invoked on methods such as clear() and removeAll() where it
|
||||
* generally makes sense to only fetch the Id values as the other property information is not
|
||||
* used.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isOnlyIds() {
|
||||
return onlyIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we should load the Collection ids into the cache.
|
||||
*/
|
||||
public boolean isLoadCache() {
|
||||
return loadCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we should load the Collection ids into the cache.
|
||||
*/
|
||||
public boolean isLoadCache() {
|
||||
return loadCache;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,22 +1,36 @@
|
||||
package com.avaje.ebeaninternal.api;
|
||||
|
||||
import com.avaje.ebean.Transaction;
|
||||
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
|
||||
|
||||
/**
|
||||
* Request for loading Associated One Beans.
|
||||
*/
|
||||
public abstract class LoadRequest {
|
||||
|
||||
protected final boolean lazy;
|
||||
protected final OrmQueryRequest<?> parentRequest;
|
||||
|
||||
protected final Transaction transaction;
|
||||
protected final Transaction transaction;
|
||||
|
||||
public LoadRequest(Transaction transaction, boolean lazy) {
|
||||
protected final boolean lazy;
|
||||
|
||||
this.transaction = transaction;
|
||||
public LoadRequest(OrmQueryRequest<?> parentRequest, boolean lazy) {
|
||||
|
||||
this.parentRequest = parentRequest;
|
||||
this.transaction = parentRequest == null ? null : parentRequest.getTransaction();
|
||||
this.lazy = lazy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the just executed secondary query with the 'root' query if 'logSecondaryQuery' is set to
|
||||
* true. This is for testing purposes to confirm the secondary query executes etc.
|
||||
*/
|
||||
public void logSecondaryQuery(SpiQuery<?> query) {
|
||||
if (parentRequest != null && parentRequest.isLogSecondaryQuery()) {
|
||||
parentRequest.getQuery().logSecondaryQuery(query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a lazy load and false if it is a secondary query.
|
||||
*/
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package com.avaje.ebeaninternal.api;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Set;
|
||||
import java.util.Collection;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
|
||||
import com.avaje.ebeaninternal.server.query.SplitName;
|
||||
import com.avaje.ebeaninternal.server.query.SqlJoinType;
|
||||
|
||||
/**
|
||||
* Holds the joins needs to support the many where predicates.
|
||||
@@ -17,12 +19,32 @@ public class ManyWhereJoins implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -6490181101871795417L;
|
||||
|
||||
private final TreeSet<String> joins = new TreeSet<String>();
|
||||
private final TreeMap<String,PropertyJoin> joins = new TreeMap<String,PropertyJoin>();
|
||||
|
||||
private StringBuilder formulaProperties = new StringBuilder();
|
||||
|
||||
private boolean formulaWithJoin;
|
||||
|
||||
/**
|
||||
* 'Mode' indicating that joins added while this is true are required to be outer joins.
|
||||
*/
|
||||
private boolean requireOuterJoins;
|
||||
|
||||
/**
|
||||
* Return the current 'mode' indicating if outer joins are currently required or not.
|
||||
*/
|
||||
public boolean isRequireOuterJoins() {
|
||||
return requireOuterJoins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'mode' to be that joins added are required to be outer joins.
|
||||
* This is set during the evaluation of disjunction predicates.
|
||||
*/
|
||||
public void setRequireOuterJoins(boolean requireOuterJoins) {
|
||||
this.requireOuterJoins = requireOuterJoins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a many where join.
|
||||
*/
|
||||
@@ -34,10 +56,10 @@ public class ManyWhereJoins implements Serializable {
|
||||
join = addManyToJoin(join, p.getName());
|
||||
}
|
||||
if (join != null){
|
||||
joins.add(join);
|
||||
addJoin(join);
|
||||
String secondaryTableJoinPrefix = p.getSecondaryTableJoinPrefix();
|
||||
if (secondaryTableJoinPrefix != null) {
|
||||
joins.add(join+"."+secondaryTableJoinPrefix);
|
||||
addJoin(join+"."+secondaryTableJoinPrefix);
|
||||
}
|
||||
addParentJoins(join);
|
||||
}
|
||||
@@ -58,11 +80,16 @@ public class ManyWhereJoins implements Serializable {
|
||||
private void addParentJoins(String join) {
|
||||
String[] split = SplitName.split(join);
|
||||
if (split[0] != null){
|
||||
joins.add(split[0]);
|
||||
addJoin(split[0]);
|
||||
addParentJoins(split[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private void addJoin(String property) {
|
||||
SqlJoinType joinType = (requireOuterJoins) ? SqlJoinType.OUTER: SqlJoinType.INNER;
|
||||
joins.put(property, new PropertyJoin(property, joinType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there are no extra many where joins.
|
||||
*/
|
||||
@@ -73,10 +100,22 @@ public class ManyWhereJoins implements Serializable {
|
||||
/**
|
||||
* Return the set of many where joins.
|
||||
*/
|
||||
public Set<String> getJoins() {
|
||||
return joins;
|
||||
public Collection<PropertyJoin> getPropertyJoins() {
|
||||
return joins.values();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the set of property names for the many where joins.
|
||||
*/
|
||||
public TreeSet<String> getPropertyNames() {
|
||||
|
||||
TreeSet<String> propertyNames = new TreeSet<String>();
|
||||
for (PropertyJoin join : joins.values()) {
|
||||
propertyNames.add(join.getProperty());
|
||||
}
|
||||
return propertyNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* In findRowCount query found a formula property with a join clause so building a select clause
|
||||
* specifically for the findRowCount query.
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.avaje.ebeaninternal.api;
|
||||
|
||||
import com.avaje.ebeaninternal.server.query.SqlJoinType;
|
||||
|
||||
/**
|
||||
* Represents a join required for a given property and whether than needs to be an outer join.
|
||||
*/
|
||||
public class PropertyJoin {
|
||||
|
||||
/**
|
||||
* The property name.
|
||||
*/
|
||||
private final String property;
|
||||
|
||||
/**
|
||||
* Set to true if the property needs to be an outer join.
|
||||
*/
|
||||
private final SqlJoinType joinType;
|
||||
|
||||
public PropertyJoin(String property, SqlJoinType joinType) {
|
||||
this.property = property;
|
||||
this.joinType = joinType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property that should be joined.
|
||||
*/
|
||||
public String getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this join is required to be an outer join.
|
||||
*/
|
||||
public SqlJoinType getSqlJoinType() {
|
||||
return joinType;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,13 +10,12 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
*/
|
||||
public interface SpiExpression extends Expression {
|
||||
|
||||
/**
|
||||
* Process "Many" properties populating ManyWhereJoins.
|
||||
* <p>
|
||||
* Predicates on Many properties require an extra independent
|
||||
* join clause.
|
||||
* </p>
|
||||
*/
|
||||
/**
|
||||
* Process "Many" properties populating ManyWhereJoins.
|
||||
* <p>
|
||||
* Predicates on Many properties require an extra independent join clause.
|
||||
* </p>
|
||||
*/
|
||||
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins);
|
||||
|
||||
/**
|
||||
|
||||
@@ -260,10 +260,25 @@ public interface SpiQuery<T> extends Query<T> {
|
||||
*/
|
||||
public Boolean isAutofetch();
|
||||
|
||||
// /**
|
||||
// * Return explicit forUpdate setting or null.
|
||||
// */
|
||||
// public boolean isForUpdate();
|
||||
/**
|
||||
* Set to true if you want to capture executed secondary queries.
|
||||
*/
|
||||
public void setLogSecondaryQuery(boolean logSecondaryQuery);
|
||||
|
||||
/**
|
||||
* Return true if executed secondary queries should be captured.
|
||||
*/
|
||||
public boolean isLogSecondaryQuery();
|
||||
|
||||
/**
|
||||
* Return the list of secondary queries that were executed.
|
||||
*/
|
||||
public List<SpiQuery<?>> getLoggedSecondaryQueries();
|
||||
|
||||
/**
|
||||
* Log an executed secondary query.
|
||||
*/
|
||||
public void logSecondaryQuery(SpiQuery<?> query);
|
||||
|
||||
/**
|
||||
* If return null then no autoFetch profiling for this query. If a
|
||||
|
||||
+10
@@ -15,10 +15,20 @@ public class CachedBeanDataFromBean {
|
||||
Object[] data = new Object[desc.getPropertyCount()];
|
||||
boolean[] loaded = new boolean[desc.getPropertyCount()];
|
||||
|
||||
BeanProperty idProperty = desc.getIdProperty();
|
||||
if (idProperty != null) {
|
||||
int propertyIndex = idProperty.getPropertyIndex();
|
||||
if (ebi.isLoadedProperty(propertyIndex)) {
|
||||
// extract the id property value
|
||||
data[propertyIndex] = idProperty.getCacheDataValue(bean);
|
||||
loaded[propertyIndex] = true;
|
||||
}
|
||||
}
|
||||
BeanProperty[] props = desc.propertiesNonMany();
|
||||
|
||||
Object naturalKey = null;
|
||||
|
||||
// extract all the non-many properties
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
BeanProperty prop = props[i];
|
||||
if (ebi.isLoadedProperty(prop.getPropertyIndex())) {
|
||||
|
||||
+22
-11
@@ -13,19 +13,17 @@ public class CachedBeanDataToBean {
|
||||
|
||||
EntityBeanIntercept ebi = bean._ebean_getIntercept();
|
||||
|
||||
|
||||
BeanProperty idProperty = desc.getIdProperty();
|
||||
if (idProperty != null) {
|
||||
// load the id property
|
||||
loadProperty(bean, cacheBeanData, ebi, idProperty);
|
||||
}
|
||||
|
||||
// load the non-many properties
|
||||
BeanProperty[] props = desc.propertiesNonMany();
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
|
||||
BeanProperty prop = props[i];
|
||||
int propertyIndex = prop.getPropertyIndex();
|
||||
if (cacheBeanData.isLoaded(propertyIndex)) {
|
||||
if (ebi.isLoadedProperty(propertyIndex)) {
|
||||
// already loaded (lazy load on partially loaded bean)
|
||||
} else {
|
||||
Object data = cacheBeanData.getData(propertyIndex);
|
||||
prop.setCacheDataValue(bean, data);
|
||||
}
|
||||
}
|
||||
loadProperty(bean, cacheBeanData, ebi, props[i]);
|
||||
}
|
||||
|
||||
BeanPropertyAssocMany<?>[] manys = desc.propertiesMany();
|
||||
@@ -38,4 +36,17 @@ public class CachedBeanDataToBean {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void loadProperty(EntityBean bean, CachedBeanData cacheBeanData, EntityBeanIntercept ebi, BeanProperty prop) {
|
||||
|
||||
int propertyIndex = prop.getPropertyIndex();
|
||||
if (cacheBeanData.isLoaded(propertyIndex)) {
|
||||
if (ebi.isLoadedProperty(propertyIndex)) {
|
||||
// already loaded (lazy load on partially loaded bean)
|
||||
} else {
|
||||
Object data = cacheBeanData.getData(propertyIndex);
|
||||
prop.setCacheDataValue(bean, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import java.sql.Types;
|
||||
import java.util.Calendar;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.avaje.ebeaninternal.server.type.ScalarTypeUUIDBinary;
|
||||
|
||||
/**
|
||||
* Default implementation of TypeConverter.
|
||||
@@ -192,6 +193,9 @@ public final class BasicTypeConverter implements Serializable {
|
||||
if (value instanceof String) {
|
||||
return UUID.fromString((String) value);
|
||||
}
|
||||
if (value instanceof byte[]) {
|
||||
return ScalarTypeUUIDBinary.convertFromBytes((byte[])value);
|
||||
}
|
||||
return (UUID) value;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,17 +128,17 @@ public abstract class BeanRequest {
|
||||
return transaction.getInternalConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if SQL should be logged for this transaction.
|
||||
*/
|
||||
public boolean isLogSql() {
|
||||
return transaction.isLogSql();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if SUMMARY information should be logged for this transaction.
|
||||
*/
|
||||
public boolean isLogSummary() {
|
||||
return transaction.isLogSummary();
|
||||
}
|
||||
/**
|
||||
* Return true if SQL should be logged for this transaction.
|
||||
*/
|
||||
public boolean isLogSql() {
|
||||
return transaction.isLogSql();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if SUMMARY information should be logged for this transaction.
|
||||
*/
|
||||
public boolean isLogSummary() {
|
||||
return transaction.isLogSummary();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,16 +53,16 @@ public class DatabasePlatformFactory {
|
||||
private DatabasePlatform byDatabaseName(String dbName) throws SQLException {
|
||||
|
||||
dbName = dbName.toLowerCase();
|
||||
if (dbName.equals("postgres83")) {
|
||||
if (dbName.equals("postgres") || dbName.equals("postgres9")) {
|
||||
return new PostgresPlatform();
|
||||
}
|
||||
if (dbName.equals("postgres8") || dbName.equals("postgres83")) {
|
||||
return new Postgres8Platform();
|
||||
}
|
||||
if (dbName.equals("oracle9")) {
|
||||
return new Oracle9Platform();
|
||||
}
|
||||
if (dbName.equals("oracle10")) {
|
||||
return new Oracle10Platform();
|
||||
}
|
||||
if (dbName.equals("oracle")) {
|
||||
if (dbName.equals("oracle") || dbName.equals("oracle10")) {
|
||||
return new Oracle10Platform();
|
||||
}
|
||||
if (dbName.equals("sqlserver2005")) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery.Mode;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType;
|
||||
import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
|
||||
|
||||
/**
|
||||
@@ -107,7 +108,10 @@ public class DefaultBeanLoader {
|
||||
BeanDescriptor<?> desc = ctx.getBeanDescriptor();
|
||||
|
||||
SpiQuery<?> query = (SpiQuery<?>) server.createQuery(many.getTargetType());
|
||||
|
||||
String orderBy = many.getLazyFetchOrderBy();
|
||||
if (orderBy != null) {
|
||||
query.orderBy(orderBy);
|
||||
}
|
||||
query.setLazyLoadForParents(idList, many);
|
||||
many.addWhereParentIdIn(query, idList);
|
||||
|
||||
@@ -139,6 +143,9 @@ public class DefaultBeanLoader {
|
||||
desc.cacheManyPropPut(many, bc, parentId);
|
||||
}
|
||||
}
|
||||
|
||||
// log the query (for testing secondary queries)
|
||||
loadRequest.logSecondaryQuery(query);
|
||||
}
|
||||
|
||||
public void loadMany(BeanCollection<?> bc, boolean onlyIds) {
|
||||
@@ -321,22 +328,33 @@ public class DefaultBeanLoader {
|
||||
// necessary but allow processing to continue until it is accessed by client code
|
||||
ebis[i].checkLazyLoadFailure();
|
||||
}
|
||||
|
||||
// log the query (for testing secondary queries)
|
||||
loadRequest.logSecondaryQuery(query);
|
||||
}
|
||||
|
||||
public void refresh(EntityBean bean) {
|
||||
refreshBeanInternal(bean, SpiQuery.Mode.REFRESH_BEAN);
|
||||
refreshBeanInternal(bean, SpiQuery.Mode.REFRESH_BEAN, -1);
|
||||
}
|
||||
|
||||
public void loadBean(EntityBeanIntercept ebi) {
|
||||
refreshBeanInternal(ebi.getOwner(), SpiQuery.Mode.LAZYLOAD_BEAN);
|
||||
refreshBeanInternal(ebi.getOwner(), SpiQuery.Mode.LAZYLOAD_BEAN, -1);
|
||||
}
|
||||
|
||||
private void refreshBeanInternal(EntityBean bean, SpiQuery.Mode mode) {
|
||||
private void refreshBeanInternal(EntityBean bean, SpiQuery.Mode mode, int embeddedOwnerIndex) {
|
||||
|
||||
EntityBeanIntercept ebi = ((EntityBean) bean)._ebean_getIntercept();;
|
||||
PersistenceContext pc = ebi.getPersistenceContext();
|
||||
|
||||
BeanDescriptor<?> desc = server.getBeanDescriptor(bean.getClass());
|
||||
if (EntityType.EMBEDDED == desc.getEntityType()) {
|
||||
// lazy loading on an embedded bean property
|
||||
EntityBean embeddedOwner = (EntityBean)ebi.getEmbeddedOwner();
|
||||
int ownerIndex = ebi.getEmbeddedOwnerIndex();
|
||||
|
||||
refreshBeanInternal(embeddedOwner, mode, ownerIndex);
|
||||
}
|
||||
|
||||
Object id = desc.getId(bean);
|
||||
|
||||
if (pc == null) {
|
||||
@@ -348,7 +366,7 @@ public class DefaultBeanLoader {
|
||||
}
|
||||
}
|
||||
|
||||
if (ebi != null) {
|
||||
if (embeddedOwnerIndex == -1) {
|
||||
if (SpiQuery.Mode.LAZYLOAD_BEAN.equals(mode) && desc.isBeanCaching()) {
|
||||
// lazy loading and the bean cache is active
|
||||
if (desc.cacheBeanLoad((EntityBean)bean, ebi, id)) {
|
||||
@@ -365,6 +383,11 @@ public class DefaultBeanLoader {
|
||||
query.setLazyLoadProperty(ebi.getLazyLoadProperty());
|
||||
}
|
||||
|
||||
if (embeddedOwnerIndex > -1) {
|
||||
String embeddedBeanPropertyName = ebi.getProperty(embeddedOwnerIndex);
|
||||
query.select("id,"+embeddedBeanPropertyName);
|
||||
}
|
||||
|
||||
// don't collect autoFetch usage profiling information
|
||||
// as we just copy the data out of these fetched beans
|
||||
// and put the data into the original bean
|
||||
@@ -373,12 +396,13 @@ public class DefaultBeanLoader {
|
||||
|
||||
query.setMode(mode);
|
||||
query.setId(id);
|
||||
// make sure the query doesn't use the cache
|
||||
if (mode.equals(SpiQuery.Mode.REFRESH_BEAN)) {
|
||||
|
||||
if (embeddedOwnerIndex > -1 || mode.equals(SpiQuery.Mode.REFRESH_BEAN)) {
|
||||
// make sure the query doesn't use the cache
|
||||
query.setUseCache(false);
|
||||
}
|
||||
|
||||
if (ebi != null && ebi.isReadOnly()) {
|
||||
if (ebi.isReadOnly()) {
|
||||
query.setReadOnly(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.avaje.ebean.Filter;
|
||||
import com.avaje.ebean.FutureIds;
|
||||
import com.avaje.ebean.FutureList;
|
||||
import com.avaje.ebean.FutureRowCount;
|
||||
import com.avaje.ebean.PagedList;
|
||||
import com.avaje.ebean.PagingList;
|
||||
import com.avaje.ebean.Query;
|
||||
import com.avaje.ebean.QueryIterator;
|
||||
@@ -96,6 +97,7 @@ import com.avaje.ebeaninternal.server.query.CallableQueryIds;
|
||||
import com.avaje.ebeaninternal.server.query.CallableQueryList;
|
||||
import com.avaje.ebeaninternal.server.query.CallableQueryRowCount;
|
||||
import com.avaje.ebeaninternal.server.query.CallableSqlQueryList;
|
||||
import com.avaje.ebeaninternal.server.query.LimitOffsetPagedList;
|
||||
import com.avaje.ebeaninternal.server.query.LimitOffsetPagingQuery;
|
||||
import com.avaje.ebeaninternal.server.query.QueryFutureIds;
|
||||
import com.avaje.ebeaninternal.server.query.QueryFutureList;
|
||||
@@ -1419,6 +1421,12 @@ public final class DefaultServer implements SpiEbeanServer {
|
||||
return new LimitOffsetPagingQuery<T>(this, spiQuery, pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> PagedList<T> findPagedList(Query<T> query, Transaction transaction, int pageIndex, int pageSize) {
|
||||
|
||||
return new LimitOffsetPagedList<T>(this, (SpiQuery<T>)query, pageIndex, pageSize);
|
||||
}
|
||||
|
||||
public <T> void findVisit(Query<T> query, QueryResultVisitor<T> visitor, Transaction t) {
|
||||
|
||||
SpiOrmQueryRequest<T> request = createQueryRequest(Type.LIST, query, t);
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.avaje.ebeaninternal.server.deploy.DeployPropertyParserMap;
|
||||
import com.avaje.ebeaninternal.server.loadcontext.DLoadContext;
|
||||
import com.avaje.ebeaninternal.server.query.CQueryPlan;
|
||||
import com.avaje.ebeaninternal.server.query.CancelableQuery;
|
||||
import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
|
||||
|
||||
/**
|
||||
* Wraps the objects involved in executing a Query.
|
||||
@@ -43,18 +44,18 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
|
||||
private final BeanFinder<T> finder;
|
||||
|
||||
private final LoadContext graphContext;
|
||||
|
||||
private final Boolean readOnly;
|
||||
|
||||
private final RawSql rawSql;
|
||||
|
||||
private LoadContext loadContext;
|
||||
|
||||
private PersistenceContext persistenceContext;
|
||||
|
||||
private HashQuery cacheKey;
|
||||
|
||||
private HashQueryPlan queryPlanHash;
|
||||
|
||||
|
||||
/**
|
||||
* Create the InternalQueryRequest.
|
||||
*/
|
||||
@@ -68,13 +69,10 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
this.queryEngine = queryEngine;
|
||||
this.query = query;
|
||||
this.readOnly = query.isReadOnly();
|
||||
|
||||
this.graphContext = new DLoadContext(ebeanServer, beanDescriptor, readOnly, query);
|
||||
graphContext.registerSecondaryQueries(query);
|
||||
}
|
||||
|
||||
public void executeSecondaryQueries(int defaultQueryBatch) {
|
||||
graphContext.executeSecondaryQueries(this, defaultQueryBatch);
|
||||
loadContext.executeSecondaryQueries(this, defaultQueryBatch);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,7 +84,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
* </p>
|
||||
*/
|
||||
public int getSecondaryQueriesMinBatchSize(int defaultQueryBatch) {
|
||||
return graphContext.getSecondaryQueriesMinBatchSize(this, defaultQueryBatch);
|
||||
return loadContext.getSecondaryQueriesMinBatchSize(this, defaultQueryBatch);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,7 +105,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
* Return the graph context for this query.
|
||||
*/
|
||||
public LoadContext getGraphContext() {
|
||||
return graphContext;
|
||||
return loadContext;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,10 +163,20 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
createdTransaction = true;
|
||||
}
|
||||
}
|
||||
// initialise the persistenceContext and loadContext
|
||||
this.persistenceContext = getPersistenceContext(query, transaction);
|
||||
this.graphContext.setPersistenceContext(persistenceContext);
|
||||
this.loadContext = new DLoadContext(this);
|
||||
this.loadContext.registerSecondaryQueries(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* For iterate queries reset the persistenceContext and loadContext.
|
||||
*/
|
||||
public void flushPersistenceContextOnIterate() {
|
||||
persistenceContext = new DefaultPersistenceContext();
|
||||
loadContext.setPersistenceContext(persistenceContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the TransactionContext either explicitly set on the query or
|
||||
* transaction scoped.
|
||||
@@ -354,13 +362,14 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
* Log the SQL if the logLevel is appropriate.
|
||||
*/
|
||||
public void logSql(String sql) {
|
||||
if (transaction.isLogSql()) {
|
||||
transaction.logSql(sql);
|
||||
}
|
||||
transaction.logSql(sql);
|
||||
}
|
||||
|
||||
public void flushPersistenceContextOnIterate() {
|
||||
beanDescriptor.flushPersistenceContextOnIterate(persistenceContext);
|
||||
/**
|
||||
* Return true if the request wants to log the secondary queries (test purpose).
|
||||
*/
|
||||
public boolean isLogSecondaryQuery() {
|
||||
return query.isLogSecondaryQuery();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,74 +34,75 @@ import com.avaje.ebeaninternal.server.transaction.BeanPersistIdMap;
|
||||
*/
|
||||
public final class PersistRequestBean<T> extends PersistRequest implements BeanPersistRequest<T> {
|
||||
|
||||
private final BeanManager<T> beanManager;
|
||||
private final BeanManager<T> beanManager;
|
||||
|
||||
private final BeanDescriptor<T> beanDescriptor;
|
||||
private final BeanDescriptor<T> beanDescriptor;
|
||||
|
||||
private final BeanPersistListener<T> beanPersistListener;
|
||||
private final BeanPersistListener<T> beanPersistListener;
|
||||
|
||||
/**
|
||||
* For per post insert update delete control.
|
||||
*/
|
||||
private final BeanPersistController controller;
|
||||
/**
|
||||
* For per post insert update delete control.
|
||||
*/
|
||||
private final BeanPersistController controller;
|
||||
|
||||
/**
|
||||
/**
|
||||
* The bean being persisted.
|
||||
*/
|
||||
private final T bean;
|
||||
|
||||
private final EntityBean entityBean;
|
||||
|
||||
/**
|
||||
* The associated intercept.
|
||||
*/
|
||||
private final EntityBeanIntercept intercept;
|
||||
private final T bean;
|
||||
|
||||
/**
|
||||
* The parent bean for unidirectional save.
|
||||
*/
|
||||
private final Object parentBean;
|
||||
private final EntityBean entityBean;
|
||||
|
||||
private final boolean dirty;
|
||||
/**
|
||||
* The associated intercept.
|
||||
*/
|
||||
private final EntityBeanIntercept intercept;
|
||||
|
||||
private ConcurrencyMode concurrencyMode;
|
||||
/**
|
||||
* The parent bean for unidirectional save.
|
||||
*/
|
||||
private final Object parentBean;
|
||||
|
||||
/**
|
||||
* The unique id used for logging summary.
|
||||
*/
|
||||
private Object idValue;
|
||||
private final boolean dirty;
|
||||
|
||||
/**
|
||||
* Hash value used to handle cascade delete both ways in a relationship.
|
||||
*/
|
||||
private Integer beanHash;
|
||||
private ConcurrencyMode concurrencyMode;
|
||||
|
||||
private boolean notifyCache;
|
||||
/**
|
||||
* The unique id used for logging summary.
|
||||
*/
|
||||
private Object idValue;
|
||||
|
||||
private boolean deleteMissingChildren;
|
||||
|
||||
private final Set<String> dirtyPropertyNames;
|
||||
/**
|
||||
* Hash value used to handle cascade delete both ways in a relationship.
|
||||
*/
|
||||
private Integer beanHash;
|
||||
|
||||
private boolean notifyCache;
|
||||
|
||||
private boolean deleteMissingChildren;
|
||||
|
||||
/**
|
||||
* Flag used to detect when only many properties where updated via a cascade. Used to ensure
|
||||
* appropriate caches are updated in that case.
|
||||
*/
|
||||
private boolean updatedManysOnly;
|
||||
|
||||
/**
|
||||
* Many properties that were cascade saved (and hence might need caches updated later).
|
||||
*/
|
||||
private boolean updatedManysOnly;
|
||||
|
||||
/**
|
||||
* Many properties that were cascade saved (and hence might need caches updated later).
|
||||
*/
|
||||
private List<BeanPropertyAssocMany<?>> updatedManys;
|
||||
|
||||
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr,
|
||||
SpiTransaction t, PersistExecute persistExecute, PersistRequest.Type type) {
|
||||
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
|
||||
PersistExecute persistExecute, PersistRequest.Type type) {
|
||||
|
||||
super(server, t, persistExecute);
|
||||
this.entityBean = (EntityBean)bean;
|
||||
super(server, t, persistExecute);
|
||||
this.entityBean = (EntityBean) bean;
|
||||
this.intercept = entityBean._ebean_getIntercept();
|
||||
this.beanManager = mgr;
|
||||
this.beanDescriptor = mgr.getBeanDescriptor();
|
||||
this.beanPersistListener = beanDescriptor.getPersistListener();
|
||||
this.beanManager = mgr;
|
||||
this.beanDescriptor = mgr.getBeanDescriptor();
|
||||
this.beanPersistListener = beanDescriptor.getPersistListener();
|
||||
this.bean = bean;
|
||||
this.parentBean = parentBean;
|
||||
this.controller = beanDescriptor.getPersistController();
|
||||
|
||||
if (PersistRequest.Type.DETERMINE != type) {
|
||||
this.type = type;
|
||||
@@ -110,30 +111,27 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
this.type = beanDescriptor.isInsertMode(intercept) ? Type.INSERT : Type.UPDATE;
|
||||
this.persistCascade = t.isPersistCascade();
|
||||
}
|
||||
|
||||
if (this.type == Type.UPDATE && intercept.isNew() ) {
|
||||
// 'stateless update' - set bean up for doing update
|
||||
intercept.setNewBeanForUpdate();
|
||||
|
||||
if (this.type == Type.UPDATE) {
|
||||
if (intercept.isNew()) {
|
||||
// 'stateless update' - set loaded properties as dirty
|
||||
intercept.setNewBeanForUpdate();
|
||||
}
|
||||
// Mark Mutable scalar properties (like Hstore) as dirty where necessary
|
||||
beanDescriptor.checkMutableProperties(intercept);
|
||||
}
|
||||
|
||||
// derive the set of property names now as we will pass them to a beanPersistListener later
|
||||
this.dirtyPropertyNames = (beanPersistListener == null) ? null : intercept.getDirtyPropertyNames();
|
||||
|
||||
this.bean = bean;
|
||||
this.parentBean = parentBean;
|
||||
this.controller = beanDescriptor.getPersistController();
|
||||
this.concurrencyMode = beanDescriptor.getConcurrencyMode(intercept);
|
||||
this.dirty = intercept.isDirty();
|
||||
}
|
||||
this.dirty = intercept.isDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is an insert request.
|
||||
*/
|
||||
/**
|
||||
* Return true if this is an insert request.
|
||||
*/
|
||||
public boolean isInsert() {
|
||||
return Type.INSERT == type;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@Override
|
||||
public Set<String> getLoadedProperties() {
|
||||
return intercept.getLoadedPropertyNames();
|
||||
}
|
||||
@@ -148,18 +146,18 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
return intercept.getDirtyValues();
|
||||
}
|
||||
|
||||
public boolean isNotify(TransactionEvent txnEvent) {
|
||||
this.notifyCache = beanDescriptor.isCacheNotify();
|
||||
return notifyCache || isNotifyPersistListener();
|
||||
}
|
||||
public boolean isNotify(TransactionEvent txnEvent) {
|
||||
this.notifyCache = beanDescriptor.isCacheNotify();
|
||||
return notifyCache || isNotifyPersistListener();
|
||||
}
|
||||
|
||||
public boolean isNotifyPersistListener() {
|
||||
return beanPersistListener != null;
|
||||
}
|
||||
public boolean isNotifyPersistListener() {
|
||||
return beanPersistListener != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify/Update the local L2 cache after the transaction has successfully committed.
|
||||
*/
|
||||
/**
|
||||
* Notify/Update the local L2 cache after the transaction has successfully committed.
|
||||
*/
|
||||
public void notifyCache() {
|
||||
if (notifyCache) {
|
||||
switch (type) {
|
||||
@@ -178,414 +176,416 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
}
|
||||
}
|
||||
|
||||
public void addToPersistMap(BeanPersistIdMap beanPersistMap) {
|
||||
public void addToPersistMap(BeanPersistIdMap beanPersistMap) {
|
||||
|
||||
beanPersistMap.add(beanDescriptor, type, idValue);
|
||||
}
|
||||
beanPersistMap.add(beanDescriptor, type, idValue);
|
||||
}
|
||||
|
||||
public boolean notifyLocalPersistListener() {
|
||||
if (beanPersistListener == null) {
|
||||
return false;
|
||||
public boolean notifyLocalPersistListener() {
|
||||
if (beanPersistListener == null) {
|
||||
return false;
|
||||
|
||||
} else {
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
return beanPersistListener.inserted(bean);
|
||||
} else {
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
return beanPersistListener.inserted(bean);
|
||||
|
||||
case UPDATE:
|
||||
return beanPersistListener.updated(bean, dirtyPropertyNames);
|
||||
case UPDATE:
|
||||
return beanPersistListener.updated(bean, intercept.getDirtyPropertyNames());
|
||||
|
||||
case DELETE:
|
||||
return beanPersistListener.deleted(bean);
|
||||
case DELETE:
|
||||
return beanPersistListener.deleted(bean);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isParent(Object o) {
|
||||
return o == parentBean;
|
||||
}
|
||||
public boolean isParent(Object o) {
|
||||
return o == parentBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this bean has been already been persisted
|
||||
* (inserted or updated) in this transaction.
|
||||
*/
|
||||
public boolean isRegisteredBean() {
|
||||
return transaction.isRegisteredBean(bean);
|
||||
}
|
||||
|
||||
public void unRegisterBean() {
|
||||
transaction.unregisterBean(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* The hash used to register the bean with the transaction.
|
||||
* <p>
|
||||
* Takes into account the class type and id value.
|
||||
* </p>
|
||||
*/
|
||||
private Integer getBeanHash() {
|
||||
if (beanHash == null) {
|
||||
Object id = beanDescriptor.getId(entityBean);
|
||||
int hc = 31 * bean.getClass().getName().hashCode();
|
||||
if (id != null) {
|
||||
hc += id.hashCode();
|
||||
}
|
||||
beanHash = Integer.valueOf(hc);
|
||||
}
|
||||
return beanHash;
|
||||
}
|
||||
|
||||
public void registerDeleteBean() {
|
||||
Integer hash = getBeanHash();
|
||||
transaction.registerDeleteBean(hash);
|
||||
}
|
||||
|
||||
public void unregisterDeleteBean() {
|
||||
Integer hash = getBeanHash();
|
||||
transaction.unregisterDeleteBean(hash);
|
||||
}
|
||||
|
||||
public boolean isRegisteredForDeleteBean() {
|
||||
if (transaction == null){
|
||||
return false;
|
||||
} else {
|
||||
Integer hash = getBeanHash();
|
||||
return transaction.isRegisteredDeleteBean(hash);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return true if this bean has been already been persisted (inserted or updated) in this
|
||||
* transaction.
|
||||
*/
|
||||
public boolean isRegisteredBean() {
|
||||
return transaction.isRegisteredBean(bean);
|
||||
}
|
||||
|
||||
public BeanManager<T> getBeanManager() {
|
||||
return beanManager;
|
||||
}
|
||||
public void unRegisterBean() {
|
||||
transaction.unregisterBean(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the BeanDescriptor for the associated bean.
|
||||
*/
|
||||
public BeanDescriptor<T> getBeanDescriptor() {
|
||||
return beanDescriptor;
|
||||
}
|
||||
/**
|
||||
* The hash used to register the bean with the transaction.
|
||||
* <p>
|
||||
* Takes into account the class type and id value.
|
||||
* </p>
|
||||
*/
|
||||
private Integer getBeanHash() {
|
||||
if (beanHash == null) {
|
||||
Object id = beanDescriptor.getId(entityBean);
|
||||
int hc = 31 * bean.getClass().getName().hashCode();
|
||||
if (id != null) {
|
||||
hc += id.hashCode();
|
||||
}
|
||||
beanHash = Integer.valueOf(hc);
|
||||
}
|
||||
return beanHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a stateless update should also delete any missing details
|
||||
* beans.
|
||||
*/
|
||||
public boolean isDeleteMissingChildren() {
|
||||
return deleteMissingChildren;
|
||||
}
|
||||
public void registerDeleteBean() {
|
||||
Integer hash = getBeanHash();
|
||||
transaction.registerDeleteBean(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if deleteMissingChildren occurs on cascade save to OneToMany or ManyToMany.
|
||||
*/
|
||||
public void setDeleteMissingChildren(boolean deleteMissingChildren) {
|
||||
this.deleteMissingChildren = deleteMissingChildren;
|
||||
}
|
||||
public void unregisterDeleteBean() {
|
||||
Integer hash = getBeanHash();
|
||||
transaction.unregisterDeleteBean(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to skip updates if we know the bean is not dirty. This is the case
|
||||
* for EntityBeans that have not been modified.
|
||||
*/
|
||||
public boolean isDirty() {
|
||||
return dirty;
|
||||
}
|
||||
public boolean isRegisteredForDeleteBean() {
|
||||
if (transaction == null) {
|
||||
return false;
|
||||
} else {
|
||||
Integer hash = getBeanHash();
|
||||
return transaction.isRegisteredDeleteBean(hash);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the concurrency mode used for this persist.
|
||||
*/
|
||||
public ConcurrencyMode getConcurrencyMode() {
|
||||
return concurrencyMode;
|
||||
}
|
||||
public BeanManager<T> getBeanManager() {
|
||||
return beanManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a description of the request. This is typically the bean class
|
||||
* name or the base table for MapBeans.
|
||||
* <p>
|
||||
* Used to determine common persist requests for queueing and statement
|
||||
* batching.
|
||||
* </p>
|
||||
*/
|
||||
public String getFullName() {
|
||||
return beanDescriptor.getFullName();
|
||||
}
|
||||
/**
|
||||
* Return the BeanDescriptor for the associated bean.
|
||||
*/
|
||||
public BeanDescriptor<T> getBeanDescriptor() {
|
||||
return beanDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean associated with this request.
|
||||
*/
|
||||
public T getBean() {
|
||||
return bean;
|
||||
}
|
||||
/**
|
||||
* Return true if a stateless update should also delete any missing details beans.
|
||||
*/
|
||||
public boolean isDeleteMissingChildren() {
|
||||
return deleteMissingChildren;
|
||||
}
|
||||
|
||||
|
||||
public EntityBean getEntityBean() {
|
||||
/**
|
||||
* Set if deleteMissingChildren occurs on cascade save to OneToMany or ManyToMany.
|
||||
*/
|
||||
public void setDeleteMissingChildren(boolean deleteMissingChildren) {
|
||||
this.deleteMissingChildren = deleteMissingChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the update after potential modifications in a BeanPersistController.
|
||||
*/
|
||||
public void postControllerPrepareUpdate() {
|
||||
if (intercept.isNew() && controller != null) {
|
||||
// 'stateless update' - set dirty properties modified in controller preUpdate
|
||||
intercept.setNewBeanForUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to skip updates if we know the bean is not dirty. This is the case for EntityBeans that
|
||||
* have not been modified.
|
||||
*/
|
||||
public boolean isDirty() {
|
||||
return dirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the concurrency mode used for this persist.
|
||||
*/
|
||||
public ConcurrencyMode getConcurrencyMode() {
|
||||
return concurrencyMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a description of the request. This is typically the bean class name or the base table
|
||||
* for MapBeans.
|
||||
* <p>
|
||||
* Used to determine common persist requests for queueing and statement batching.
|
||||
* </p>
|
||||
*/
|
||||
public String getFullName() {
|
||||
return beanDescriptor.getFullName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean associated with this request.
|
||||
*/
|
||||
public T getBean() {
|
||||
return bean;
|
||||
}
|
||||
|
||||
public EntityBean getEntityBean() {
|
||||
return entityBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Id value for the bean.
|
||||
*/
|
||||
public Object getBeanId() {
|
||||
return beanDescriptor.getId(entityBean);
|
||||
}
|
||||
* Return the Id value for the bean.
|
||||
*/
|
||||
public Object getBeanId() {
|
||||
return beanDescriptor.getId(entityBean);
|
||||
}
|
||||
|
||||
public BeanDelta createDeltaBean() {
|
||||
return new BeanDelta(beanDescriptor, getBeanId());
|
||||
}
|
||||
public BeanDelta createDeltaBean() {
|
||||
return new BeanDelta(beanDescriptor, getBeanId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parent bean for cascading save with unidirectional
|
||||
* relationship.
|
||||
*/
|
||||
public Object getParentBean() {
|
||||
return parentBean;
|
||||
}
|
||||
/**
|
||||
* Return the parent bean for cascading save with unidirectional relationship.
|
||||
*/
|
||||
public Object getParentBean() {
|
||||
return parentBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the controller if there is one associated with this type of bean.
|
||||
* This returns null if there is no controller associated.
|
||||
*/
|
||||
public BeanPersistController getBeanController() {
|
||||
return controller;
|
||||
}
|
||||
/**
|
||||
* Return the controller if there is one associated with this type of bean. This returns null if
|
||||
* there is no controller associated.
|
||||
*/
|
||||
public BeanPersistController getBeanController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the intercept if there is one.
|
||||
*/
|
||||
public EntityBeanIntercept getEntityBeanIntercept() {
|
||||
return intercept;
|
||||
}
|
||||
/**
|
||||
* Return the intercept if there is one.
|
||||
*/
|
||||
public EntityBeanIntercept getEntityBeanIntercept() {
|
||||
return intercept;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property is loaded (full bean or included in partial
|
||||
* bean).
|
||||
*/
|
||||
public boolean isLoadedProperty(BeanProperty prop) {
|
||||
return intercept.isLoadedProperty(prop.getPropertyIndex());
|
||||
}
|
||||
/**
|
||||
* Return true if this property is loaded (full bean or included in partial bean).
|
||||
*/
|
||||
public boolean isLoadedProperty(BeanProperty prop) {
|
||||
return intercept.isLoadedProperty(prop.getPropertyIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeNow() {
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
persistExecute.executeInsertBean(this);
|
||||
return -1;
|
||||
@Override
|
||||
public int executeNow() {
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
persistExecute.executeInsertBean(this);
|
||||
return -1;
|
||||
|
||||
case UPDATE:
|
||||
persistExecute.executeUpdateBean(this);
|
||||
return -1;
|
||||
case UPDATE:
|
||||
persistExecute.executeUpdateBean(this);
|
||||
return -1;
|
||||
|
||||
case DELETE:
|
||||
persistExecute.executeDeleteBean(this);
|
||||
return -1;
|
||||
case DELETE:
|
||||
persistExecute.executeDeleteBean(this);
|
||||
return -1;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Invalid type " + type);
|
||||
}
|
||||
}
|
||||
default:
|
||||
throw new RuntimeException("Invalid type " + type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeOrQueue() {
|
||||
@Override
|
||||
public int executeOrQueue() {
|
||||
|
||||
boolean batch = transaction.isBatchThisRequest();
|
||||
boolean batch = transaction.isBatchThisRequest();
|
||||
|
||||
BatchControl control = transaction.getBatchControl();
|
||||
if (control != null) {
|
||||
return control.executeOrQueue(this, batch);
|
||||
}
|
||||
if (batch) {
|
||||
control = persistExecute.createBatchControl(transaction);
|
||||
return control.executeOrQueue(this, batch);
|
||||
BatchControl control = transaction.getBatchControl();
|
||||
if (control != null) {
|
||||
return control.executeOrQueue(this, batch);
|
||||
}
|
||||
if (batch) {
|
||||
control = persistExecute.createBatchControl(transaction);
|
||||
return control.executeOrQueue(this, batch);
|
||||
|
||||
} else {
|
||||
return executeNow();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return executeNow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the generated key back to the bean. Only used for inserts with
|
||||
* getGeneratedKeys.
|
||||
*/
|
||||
public void setGeneratedKey(Object idValue) {
|
||||
if (idValue != null) {
|
||||
// remember it for logging summary
|
||||
this.idValue = beanDescriptor.convertSetId(idValue, entityBean);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set the generated key back to the bean. Only used for inserts with getGeneratedKeys.
|
||||
*/
|
||||
public void setGeneratedKey(Object idValue) {
|
||||
if (idValue != null) {
|
||||
// remember it for logging summary
|
||||
this.idValue = beanDescriptor.convertSetId(idValue, entityBean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Id value that was bound. Used for the purposes of logging summary
|
||||
* information on this request.
|
||||
*/
|
||||
public void setBoundId(Object idValue) {
|
||||
this.idValue = idValue;
|
||||
}
|
||||
/**
|
||||
* Set the Id value that was bound. Used for the purposes of logging summary information on this
|
||||
* request.
|
||||
*/
|
||||
public void setBoundId(Object idValue) {
|
||||
this.idValue = idValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for optimistic concurrency exception.
|
||||
*/
|
||||
public final void checkRowCount(int rowCount) throws SQLException {
|
||||
if (rowCount != 1) {
|
||||
String m = Message.msg("persist.conc2", "" + rowCount);
|
||||
throw new OptimisticLockException(m, null, bean);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Check for optimistic concurrency exception.
|
||||
*/
|
||||
public final void checkRowCount(int rowCount) throws SQLException {
|
||||
if (rowCount != 1) {
|
||||
String m = Message.msg("persist.conc2", "" + rowCount);
|
||||
throw new OptimisticLockException(m, null, bean);
|
||||
}
|
||||
}
|
||||
|
||||
public void postDelete() {
|
||||
|
||||
// Delete the bean from the PersistenceContent
|
||||
transaction.getPersistenceContext().clear(beanDescriptor.getBeanType(), idValue);
|
||||
// Delete from cache early even if transaction fails
|
||||
beanDescriptor.cacheHandleDelete(idValue, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post processing.
|
||||
*/
|
||||
public void postExecute() throws SQLException {
|
||||
public void postDelete() {
|
||||
|
||||
if (controller != null) {
|
||||
controllerPost();
|
||||
}
|
||||
// Delete the bean from the PersistenceContent
|
||||
transaction.getPersistenceContext().clear(beanDescriptor.getBeanType(), idValue);
|
||||
// Delete from cache early even if transaction fails
|
||||
beanDescriptor.cacheHandleDelete(idValue, this);
|
||||
}
|
||||
|
||||
if (intercept != null) {
|
||||
// if bean persisted again then should result in an update
|
||||
intercept.setLoaded();
|
||||
}
|
||||
/**
|
||||
* Post processing.
|
||||
*/
|
||||
public void postExecute() throws SQLException {
|
||||
|
||||
addEvent();
|
||||
if (controller != null) {
|
||||
controllerPost();
|
||||
}
|
||||
|
||||
if (isLogSummary()) {
|
||||
logSummary();
|
||||
}
|
||||
}
|
||||
if (intercept != null) {
|
||||
// if bean persisted again then should result in an update
|
||||
intercept.setLoaded();
|
||||
}
|
||||
|
||||
private void controllerPost() {
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
controller.postInsert(this);
|
||||
break;
|
||||
case UPDATE:
|
||||
controller.postUpdate(this);
|
||||
break;
|
||||
case DELETE:
|
||||
controller.postDelete(this);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
addEvent();
|
||||
|
||||
private void logSummary() {
|
||||
if (isLogSummary()) {
|
||||
logSummary();
|
||||
}
|
||||
}
|
||||
|
||||
String name = beanDescriptor.getName();
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
transaction.logSummary("Inserted [" + name + "] [" + idValue + "]");
|
||||
break;
|
||||
case UPDATE:
|
||||
transaction.logSummary("Updated [" + name + "] [" + idValue + "]");
|
||||
break;
|
||||
case DELETE:
|
||||
transaction.logSummary("Deleted [" + name + "] [" + idValue + "]");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
private void controllerPost() {
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
controller.postInsert(this);
|
||||
break;
|
||||
case UPDATE:
|
||||
controller.postUpdate(this);
|
||||
break;
|
||||
case DELETE:
|
||||
controller.postDelete(this);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the bean to the TransactionEvent. This will be used by
|
||||
* TransactionManager to synch Cache, Cluster and text indexes.
|
||||
*/
|
||||
private void addEvent() {
|
||||
private void logSummary() {
|
||||
|
||||
TransactionEvent event = transaction.getEvent();
|
||||
if (event != null) {
|
||||
event.add(this);
|
||||
}
|
||||
}
|
||||
String name = beanDescriptor.getName();
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
transaction.logSummary("Inserted [" + name + "] [" + idValue + "]");
|
||||
break;
|
||||
case UPDATE:
|
||||
transaction.logSummary("Updated [" + name + "] [" + idValue + "]");
|
||||
break;
|
||||
case DELETE:
|
||||
transaction.logSummary("Deleted [" + name + "] [" + idValue + "]");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the concurrency mode depending on fully/partially populated
|
||||
* bean.
|
||||
* <p>
|
||||
* Specifically with version concurrency we want to check that the version
|
||||
* property was one of the loaded properties.
|
||||
* </p>
|
||||
*/
|
||||
public ConcurrencyMode determineConcurrencyMode() {
|
||||
|
||||
// 'partial bean' update/delete...
|
||||
if (concurrencyMode.equals(ConcurrencyMode.VERSION)) {
|
||||
// check the version property was loaded
|
||||
BeanProperty prop = beanDescriptor.getVersionProperty();
|
||||
if (prop != null && intercept.isLoadedProperty(prop.getPropertyIndex())) {
|
||||
// OK to use version property
|
||||
} else {
|
||||
concurrencyMode = ConcurrencyMode.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
return concurrencyMode;
|
||||
}
|
||||
/**
|
||||
* Add the bean to the TransactionEvent. This will be used by TransactionManager to synch Cache,
|
||||
* Cluster and text indexes.
|
||||
*/
|
||||
private void addEvent() {
|
||||
|
||||
/**
|
||||
* Return true if the update DML/SQL must be dynamically generated.
|
||||
* <p>
|
||||
* This is the case for updates/deletes of partially populated beans.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isDynamicUpdateSql() {
|
||||
return beanDescriptor.isUpdateChangesOnly() || !intercept.isFullyLoadedBean();
|
||||
}
|
||||
TransactionEvent event = transaction.getEvent();
|
||||
if (event != null) {
|
||||
event.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a GenerateDmlRequest used to generate the DML.
|
||||
* <p>
|
||||
* Will used changed properties or loaded properties depending on the
|
||||
* BeanDescriptor.isUpdateChangesOnly() value.
|
||||
* </p>
|
||||
*/
|
||||
public GenerateDmlRequest createGenerateDmlRequest(boolean emptyStringAsNull) {
|
||||
return new GenerateDmlRequest(emptyStringAsNull, intercept, beanDescriptor.isUpdateChangesOnly());
|
||||
}
|
||||
/**
|
||||
* Determine the concurrency mode depending on fully/partially populated bean.
|
||||
* <p>
|
||||
* Specifically with version concurrency we want to check that the version property was one of the
|
||||
* loaded properties.
|
||||
* </p>
|
||||
*/
|
||||
public ConcurrencyMode determineConcurrencyMode() {
|
||||
|
||||
/**
|
||||
* Test if the property value has changed and if so include it in the
|
||||
* update.
|
||||
*/
|
||||
public boolean isAddToUpdate(BeanProperty prop) {
|
||||
return intercept.isDirtyProperty(prop.getPropertyIndex());
|
||||
}
|
||||
// 'partial bean' update/delete...
|
||||
if (concurrencyMode.equals(ConcurrencyMode.VERSION)) {
|
||||
// check the version property was loaded
|
||||
BeanProperty prop = beanDescriptor.getVersionProperty();
|
||||
if (prop != null && intercept.isLoadedProperty(prop.getPropertyIndex())) {
|
||||
// OK to use version property
|
||||
} else {
|
||||
concurrencyMode = ConcurrencyMode.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
return concurrencyMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the update DML/SQL must be dynamically generated.
|
||||
* <p>
|
||||
* This is the case for updates/deletes of partially populated beans.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isDynamicUpdateSql() {
|
||||
return beanDescriptor.isUpdateChangesOnly() || !intercept.isFullyLoadedBean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a GenerateDmlRequest used to generate the DML.
|
||||
* <p>
|
||||
* Will used changed properties or loaded properties depending on the
|
||||
* BeanDescriptor.isUpdateChangesOnly() value.
|
||||
* </p>
|
||||
*/
|
||||
public GenerateDmlRequest createGenerateDmlRequest(boolean emptyStringAsNull) {
|
||||
return new GenerateDmlRequest(emptyStringAsNull, intercept, beanDescriptor.isUpdateChangesOnly());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the property value has changed and if so include it in the update.
|
||||
*/
|
||||
public boolean isAddToUpdate(BeanProperty prop) {
|
||||
return intercept.isDirtyProperty(prop.getPropertyIndex());
|
||||
}
|
||||
|
||||
public List<DerivedRelationshipData> getDerivedRelationships() {
|
||||
return transaction.getDerivedRelationship(bean);
|
||||
}
|
||||
|
||||
public void postInsert() {
|
||||
// mark all properties as loaded after an insert to support immediate update
|
||||
// mark all properties as loaded after an insert to support immediate update
|
||||
int len = intercept.getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
intercept.setLoadedProperty(i);
|
||||
intercept.setLoadedProperty(i);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isReference() {
|
||||
return beanDescriptor.isReference(intercept);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This many property has been cascade saved. Keep note of this and update the 'many property'
|
||||
* cache on post commit.
|
||||
*/
|
||||
public void addUpdatedManyProperty(BeanPropertyAssocMany<?> updatedAssocMany) {
|
||||
//if (notifyCache) {
|
||||
if (updatedManys == null) {
|
||||
updatedManys = new ArrayList<BeanPropertyAssocMany<?>>(5);
|
||||
}
|
||||
updatedManys.add(updatedAssocMany);
|
||||
//}
|
||||
// if (notifyCache) {
|
||||
if (updatedManys == null) {
|
||||
updatedManys = new ArrayList<BeanPropertyAssocMany<?>>(5);
|
||||
}
|
||||
updatedManys.add(updatedAssocMany);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,10 @@ package com.avaje.ebeaninternal.server.ddl;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.avaje.ebean.config.TableName;
|
||||
import com.avaje.ebean.config.dbplatform.DbDdlSyntax;
|
||||
import com.avaje.ebean.config.dbplatform.IdType;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
@@ -12,8 +16,6 @@ import com.avaje.ebeaninternal.server.deploy.TableJoin;
|
||||
import com.avaje.ebeaninternal.server.deploy.TableJoinColumn;
|
||||
import com.avaje.ebeaninternal.server.deploy.id.ImportedId;
|
||||
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Used as part of CreateTableVisitor to generated the create table DDL script.
|
||||
@@ -71,7 +73,7 @@ public class CreateTableColumnVisitor extends BaseTablePropertyVisitor {
|
||||
|
||||
private StringBuilder createUniqueConstraintBuffer(String table, String column) {
|
||||
|
||||
String uqConstraintName = "uq_" + table + "_" + column;
|
||||
String uqConstraintName = "uq_"+TableName.parse(table)+"_"+column;
|
||||
|
||||
if (uqConstraintName.length() > ddl.getMaxConstraintNameLength()) {
|
||||
uqConstraintName = uqConstraintName.substring(0, ddl.getMaxConstraintNameLength());
|
||||
@@ -153,11 +155,12 @@ public class CreateTableColumnVisitor extends BaseTablePropertyVisitor {
|
||||
parent.writeColumnName(p.getDbColumn(), p);
|
||||
|
||||
String columnDefn = ctx.getColumnDefn(p);
|
||||
ctx.write(columnDefn);
|
||||
|
||||
|
||||
boolean identity = isIdentity(p);
|
||||
if (identity) {
|
||||
writeIdentity();
|
||||
ctx.write(ddl.getIdentityColumnDefn(columnDefn));
|
||||
} else {
|
||||
ctx.write(columnDefn);
|
||||
}
|
||||
|
||||
if (p.isId() && ddl.isInlinePrimaryKeyConstraint()){
|
||||
@@ -186,13 +189,6 @@ public class CreateTableColumnVisitor extends BaseTablePropertyVisitor {
|
||||
expr.append(p.getDbColumn()).append(")");
|
||||
return expr.toString();
|
||||
}
|
||||
|
||||
protected void writeIdentity() {
|
||||
String identity = ddl.getIdentity();
|
||||
if (identity != null && identity.length() > 0) {
|
||||
ctx.write(" ").write(identity);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeIdentitySuffix() {
|
||||
String identity = ddl.getIdentitySuffix();
|
||||
|
||||
@@ -4,6 +4,10 @@ import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.avaje.ebean.config.TableName;
|
||||
import com.avaje.ebean.config.dbplatform.DbDdlSyntax;
|
||||
import com.avaje.ebean.config.dbplatform.DbType;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
@@ -13,8 +17,6 @@ import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound;
|
||||
import com.avaje.ebeaninternal.server.deploy.CompoundUniqueContraint;
|
||||
import com.avaje.ebeaninternal.server.deploy.InheritInfo;
|
||||
import com.avaje.ebeaninternal.server.deploy.parse.SqlReservedWords;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Used to generated the create table DDL script.
|
||||
@@ -23,23 +25,22 @@ public class CreateTableVisitor extends AbstractBeanVisitor {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CreateTableVisitor.class);
|
||||
|
||||
final DdlGenContext ctx;
|
||||
private final DdlGenContext ctx;
|
||||
|
||||
final PropertyVisitor pv;
|
||||
private final PropertyVisitor pv;
|
||||
|
||||
final DbDdlSyntax ddl;
|
||||
private final DbDdlSyntax ddl;
|
||||
|
||||
final int columnNameWidth;
|
||||
private final int columnNameWidth;
|
||||
|
||||
// avoid writing columns twice, e.g. when used in associations with insertable=false and updateable=false
|
||||
private final Set<String> wroteColumns = new HashSet<String>();
|
||||
|
||||
private ArrayList<String> checkConstraints = new ArrayList<String>();
|
||||
private ArrayList<String> checkConstraints = new ArrayList<String>();
|
||||
|
||||
private ArrayList<String> uniqueConstraints = new ArrayList<String>();
|
||||
private ArrayList<String> uniqueConstraints = new ArrayList<String>();
|
||||
|
||||
private String table;
|
||||
private String schema;
|
||||
private String table;
|
||||
|
||||
public Set<String> getWroteColumns() {
|
||||
return wroteColumns;
|
||||
@@ -53,235 +54,211 @@ public class CreateTableVisitor extends AbstractBeanVisitor {
|
||||
this.pv = new CreateTableColumnVisitor(this, ctx);
|
||||
}
|
||||
|
||||
public boolean isDbColumnWritten(String dbColumn) {
|
||||
// Columns are not case sensitive - user lower case
|
||||
// as e.g. @JoinColumn(s) may use a different case
|
||||
return wroteColumns.contains(dbColumn.toLowerCase());
|
||||
}
|
||||
public boolean isDbColumnWritten(String dbColumn) {
|
||||
// Columns are not case sensitive - user lower case
|
||||
// as e.g. @JoinColumn(s) may use a different case
|
||||
return wroteColumns.contains(dbColumn.toLowerCase());
|
||||
}
|
||||
|
||||
public void addDbColumnWritten(String dbColumn){
|
||||
// Column names are case insensitive
|
||||
wroteColumns.add(dbColumn.toLowerCase());
|
||||
}
|
||||
public void addDbColumnWritten(String dbColumn) {
|
||||
// Column names are case insensitive
|
||||
wroteColumns.add(dbColumn.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the table name including a check for SQL reserved words.
|
||||
*/
|
||||
protected void writeTableName(BeanDescriptor<?> descriptor) {
|
||||
|
||||
String tableName = descriptor.getBaseTable();
|
||||
int dotPos = tableName.lastIndexOf('.');
|
||||
if (dotPos > -1){
|
||||
schema = tableName.substring(0, dotPos);
|
||||
table = tableName.substring(dotPos+1);
|
||||
} else {
|
||||
table = tableName;
|
||||
}
|
||||
|
||||
if (SqlReservedWords.isKeyword(table)) {
|
||||
logger.warn("Table name ["+table+"] is a suspected SQL reserved word for bean "+descriptor.getFullName());
|
||||
}
|
||||
|
||||
ctx.write(tableName);
|
||||
}
|
||||
|
||||
protected String getTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
protected String getSchema() {
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the column name including a check for the SQL reserved words.
|
||||
* @param p
|
||||
*/
|
||||
protected void writeColumnName(String columnName, BeanProperty p) {
|
||||
|
||||
addDbColumnWritten(columnName);
|
||||
|
||||
if (SqlReservedWords.isKeyword(columnName)) {
|
||||
String propName = p == null ? "(Unknown)" : p.getFullBeanName();
|
||||
logger.warn("Column name ["+columnName+"] is a suspected SQL reserved word for property "+propName);
|
||||
}
|
||||
|
||||
ctx.write(" ").write(columnName, columnNameWidth).write(" ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a check constraint for the property if required.
|
||||
* <p>
|
||||
* Typically check constraint based on Enum mapping values.
|
||||
* </p>
|
||||
*/
|
||||
protected void addCheckConstraint(BeanProperty p, String prefix, String constraintExpression) {
|
||||
|
||||
if (p != null && constraintExpression != null){
|
||||
|
||||
// build constraint clause
|
||||
String s = "constraint "+getConstraintName(prefix, p)+" "+constraintExpression;
|
||||
|
||||
// add to list as we render all check constraints just prior to primary key
|
||||
checkConstraints.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getConstraintName(String prefix, BeanProperty p) {
|
||||
return prefix + table + "_" + p.getDbColumn();
|
||||
}
|
||||
|
||||
protected void addUniqueConstraint(String constraintExpression) {
|
||||
uniqueConstraints.add(constraintExpression);
|
||||
}
|
||||
/**
|
||||
* Write the table name including a check for SQL reserved words.
|
||||
*/
|
||||
protected void writeTableName(BeanDescriptor<?> descriptor) {
|
||||
|
||||
protected void addCheckConstraint(String constraintExpression) {
|
||||
checkConstraints.add(constraintExpression);
|
||||
}
|
||||
|
||||
protected void addCheckConstraint(BeanProperty p) {
|
||||
addCheckConstraint(p,"ck_", p.getDbConstraintExpression());
|
||||
}
|
||||
|
||||
public boolean visitBean(BeanDescriptor<?> descriptor) {
|
||||
|
||||
wroteColumns.clear();
|
||||
|
||||
if (!descriptor.isInheritanceRoot()){
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx.write("create table ");
|
||||
writeTableName(descriptor);
|
||||
ctx.write(" (").writeNewLine();
|
||||
|
||||
InheritInfo inheritInfo = descriptor.getInheritInfo();
|
||||
if (inheritInfo != null && inheritInfo.isRoot()){
|
||||
String discColumn = inheritInfo.getDiscriminatorColumn();
|
||||
int discType = inheritInfo.getDiscriminatorType();
|
||||
int discLength = inheritInfo.getDiscriminatorLength();
|
||||
DbType dbType = ctx.getDbTypeMap().get(discType);
|
||||
String discDbType = dbType.renderType(discLength, 0);
|
||||
|
||||
writeColumnName(discColumn, null);
|
||||
ctx.write(discDbType);
|
||||
ctx.write(" not null,");
|
||||
ctx.writeNewLine();
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void visitBeanEnd(BeanDescriptor<?> descriptor) {
|
||||
// parse to remove any catalog or schema prefix on the table
|
||||
table = TableName.parse(descriptor.getBaseTable());
|
||||
ctx.write(descriptor.getBaseTable());
|
||||
}
|
||||
|
||||
visitInheritanceProperties(descriptor, pv);
|
||||
|
||||
if (checkConstraints.size() > 0){
|
||||
for (String checkConstraint : checkConstraints) {
|
||||
ctx.write(" ").write(checkConstraint).write(",").writeNewLine();
|
||||
}
|
||||
checkConstraints = new ArrayList<String>();
|
||||
}
|
||||
|
||||
if (uniqueConstraints.size() > 0){
|
||||
for (String constraint : uniqueConstraints) {
|
||||
ctx.write(" ").write(constraint).write(",").writeNewLine();
|
||||
}
|
||||
uniqueConstraints = new ArrayList<String>();
|
||||
}
|
||||
|
||||
CompoundUniqueContraint[] compoundUniqueConstraints = descriptor.getCompoundUniqueConstraints();
|
||||
if (compoundUniqueConstraints != null){
|
||||
String table = descriptor.getBaseTable();
|
||||
for (int i = 0; i < compoundUniqueConstraints.length; i++) {
|
||||
String constraint = createUniqueConstraint(table, i, compoundUniqueConstraints[i]);
|
||||
ctx.write(" ").write(constraint).write(",").writeNewLine();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BeanProperty idProp = descriptor.getIdProperty();
|
||||
/**
|
||||
* Write the column name including a check for the SQL reserved words.
|
||||
*/
|
||||
protected void writeColumnName(String columnName, BeanProperty p) {
|
||||
|
||||
if (idProp == null){
|
||||
// No comma + new line
|
||||
ctx.removeLast().removeLast();
|
||||
} else if (ddl.isInlinePrimaryKeyConstraint()) {
|
||||
// The Primary Key constraint was inlined with the column
|
||||
// ... No comma + new line
|
||||
ctx.removeLast().removeLast();
|
||||
|
||||
} else {
|
||||
// Add the primay key constraint
|
||||
String pkName = ddl.getPrimaryKeyName(table);
|
||||
ctx.write(" constraint ").write(pkName).write(" primary key (");
|
||||
|
||||
VisitorUtil.visit(idProp, new AbstractPropertyVisitor() {
|
||||
|
||||
@Override
|
||||
public void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne<?> embedded) {
|
||||
ctx.write(p.getDbColumn()).write(", ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitScalar(BeanProperty p) {
|
||||
ctx.write(p.getDbColumn()).write(", ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitCompoundScalar(BeanPropertyCompound compound, BeanProperty p) {
|
||||
ctx.write(p.getDbColumn()).write(", ");
|
||||
}
|
||||
|
||||
});
|
||||
// remove the last comma, end of PK
|
||||
ctx.removeLast().write(")");
|
||||
}
|
||||
|
||||
// end of table
|
||||
ctx.write(")").writeNewLine();
|
||||
ctx.write(";").writeNewLine().writeNewLine();
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
private String createUniqueConstraint(String table, int idx, CompoundUniqueContraint uc) {
|
||||
|
||||
String uqConstraintName = "uq_"+table+"_"+(idx+1) ;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("constraint ")
|
||||
.append(uqConstraintName)
|
||||
.append(" unique (");
|
||||
|
||||
String[] columns = uc.getColumns();
|
||||
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
if (i > 0){
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append(columns[i]);
|
||||
}
|
||||
sb.append(")");
|
||||
|
||||
return sb.toString();
|
||||
addDbColumnWritten(columnName);
|
||||
|
||||
if (SqlReservedWords.isKeyword(columnName)) {
|
||||
String propName = p == null ? "(Unknown)" : p.getFullBeanName();
|
||||
logger.warn("Column name [" + columnName + "] is a suspected SQL reserved word for property " + propName);
|
||||
}
|
||||
|
||||
ctx.write(" ").write(columnName, columnNameWidth).write(" ");
|
||||
}
|
||||
|
||||
public void visitBeanDescriptorEnd() {
|
||||
ctx.write(");").writeNewLine().writeNewLine();
|
||||
}
|
||||
/**
|
||||
* Build a check constraint for the property if required.
|
||||
* <p>
|
||||
* Typically check constraint based on Enum mapping values.
|
||||
* </p>
|
||||
*/
|
||||
protected void addCheckConstraint(BeanProperty p, String prefix, String constraintExpression) {
|
||||
|
||||
if (p != null && constraintExpression != null) {
|
||||
|
||||
public PropertyVisitor visitProperty(BeanProperty p) {
|
||||
return pv;
|
||||
}
|
||||
// build constraint clause
|
||||
String s = "constraint " + getConstraintName(prefix, p) + " " + constraintExpression;
|
||||
|
||||
public void visitBegin() {
|
||||
|
||||
}
|
||||
// add to list as we render all check constraints just prior to primary key
|
||||
checkConstraints.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
ctx.addIntersectionCreateTables();
|
||||
ctx.flush();
|
||||
}
|
||||
protected String getConstraintName(String prefix, BeanProperty p) {
|
||||
return prefix + table + "_" + p.getDbColumn();
|
||||
}
|
||||
|
||||
protected void addUniqueConstraint(String constraintExpression) {
|
||||
uniqueConstraints.add(constraintExpression);
|
||||
}
|
||||
|
||||
protected void addCheckConstraint(String constraintExpression) {
|
||||
checkConstraints.add(constraintExpression);
|
||||
}
|
||||
|
||||
protected void addCheckConstraint(BeanProperty p) {
|
||||
addCheckConstraint(p, "ck_", p.getDbConstraintExpression());
|
||||
}
|
||||
|
||||
public boolean visitBean(BeanDescriptor<?> descriptor) {
|
||||
|
||||
wroteColumns.clear();
|
||||
|
||||
if (!descriptor.isInheritanceRoot()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx.write("create table ");
|
||||
writeTableName(descriptor);
|
||||
ctx.write(" (").writeNewLine();
|
||||
|
||||
InheritInfo inheritInfo = descriptor.getInheritInfo();
|
||||
if (inheritInfo != null && inheritInfo.isRoot()) {
|
||||
String discColumn = inheritInfo.getDiscriminatorColumn();
|
||||
int discType = inheritInfo.getDiscriminatorType();
|
||||
int discLength = inheritInfo.getDiscriminatorLength();
|
||||
DbType dbType = ctx.getDbTypeMap().get(discType);
|
||||
String discDbType = dbType.renderType(discLength, 0);
|
||||
|
||||
writeColumnName(discColumn, null);
|
||||
ctx.write(discDbType);
|
||||
ctx.write(" not null,");
|
||||
ctx.writeNewLine();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void visitBeanEnd(BeanDescriptor<?> descriptor) {
|
||||
|
||||
visitInheritanceProperties(descriptor, pv);
|
||||
|
||||
if (checkConstraints.size() > 0) {
|
||||
for (String checkConstraint : checkConstraints) {
|
||||
ctx.write(" ").write(checkConstraint).write(",").writeNewLine();
|
||||
}
|
||||
checkConstraints = new ArrayList<String>();
|
||||
}
|
||||
|
||||
if (uniqueConstraints.size() > 0) {
|
||||
for (String constraint : uniqueConstraints) {
|
||||
ctx.write(" ").write(constraint).write(",").writeNewLine();
|
||||
}
|
||||
uniqueConstraints = new ArrayList<String>();
|
||||
}
|
||||
|
||||
CompoundUniqueContraint[] compoundUniqueConstraints = descriptor.getCompoundUniqueConstraints();
|
||||
if (compoundUniqueConstraints != null) {
|
||||
String table = descriptor.getBaseTable();
|
||||
for (int i = 0; i < compoundUniqueConstraints.length; i++) {
|
||||
String constraint = createUniqueConstraint(table, i, compoundUniqueConstraints[i]);
|
||||
ctx.write(" ").write(constraint).write(",").writeNewLine();
|
||||
}
|
||||
}
|
||||
|
||||
BeanProperty idProp = descriptor.getIdProperty();
|
||||
|
||||
if (idProp == null) {
|
||||
// No comma + new line
|
||||
ctx.removeLast().removeLast();
|
||||
} else if (ddl.isInlinePrimaryKeyConstraint()) {
|
||||
// The Primary Key constraint was inlined with the column
|
||||
// ... No comma + new line
|
||||
ctx.removeLast().removeLast();
|
||||
|
||||
} else {
|
||||
// Add the primay key constraint
|
||||
String pkName = ddl.getPrimaryKeyName(table);
|
||||
ctx.write(" constraint ").write(pkName).write(" primary key (");
|
||||
|
||||
VisitorUtil.visit(idProp, new AbstractPropertyVisitor() {
|
||||
|
||||
@Override
|
||||
public void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne<?> embedded) {
|
||||
ctx.write(p.getDbColumn()).write(", ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitScalar(BeanProperty p) {
|
||||
ctx.write(p.getDbColumn()).write(", ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitCompoundScalar(BeanPropertyCompound compound, BeanProperty p) {
|
||||
ctx.write(p.getDbColumn()).write(", ");
|
||||
}
|
||||
|
||||
});
|
||||
// remove the last comma, end of PK
|
||||
ctx.removeLast().write(")");
|
||||
}
|
||||
|
||||
// end of table
|
||||
ctx.write(")").writeNewLine();
|
||||
ctx.write(";").writeNewLine().writeNewLine();
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
private String createUniqueConstraint(String table, int idx, CompoundUniqueContraint uc) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(50);
|
||||
sb.append("constraint ")
|
||||
.append("uq_").append(TableName.parse(table)).append("_").append(idx + 1)
|
||||
.append(" unique (");
|
||||
|
||||
String[] columns = uc.getColumns();
|
||||
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append(columns[i]);
|
||||
}
|
||||
sb.append(")");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void visitBeanDescriptorEnd() {
|
||||
ctx.write(");").writeNewLine().writeNewLine();
|
||||
}
|
||||
|
||||
public PropertyVisitor visitProperty(BeanProperty p) {
|
||||
return pv;
|
||||
}
|
||||
|
||||
public void visitBegin() {
|
||||
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
ctx.addIntersectionCreateTables();
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -210,11 +210,22 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
|
||||
private final int versionPropertyIndex;
|
||||
|
||||
/**
|
||||
* Properties that are initialised in the constructor need to be 'unloaded' to support partial object queries.
|
||||
*/
|
||||
private final int[] unloadProperties;
|
||||
|
||||
/**
|
||||
* Properties local to this type (not from a super type).
|
||||
*/
|
||||
private final BeanProperty[] propertiesLocal;
|
||||
|
||||
/**
|
||||
* Scalar mutable properties (need to dirty check on update).
|
||||
*/
|
||||
private final BeanProperty[] propertiesMutable;
|
||||
|
||||
|
||||
private final BeanPropertyAssocOne<?> unidirectional;
|
||||
|
||||
/**
|
||||
@@ -386,6 +397,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
this.propertiesBaseCompound = listHelper.getBaseCompound();
|
||||
this.propertiesEmbedded = listHelper.getEmbedded();
|
||||
this.propertiesLocal = listHelper.getLocal();
|
||||
this.propertiesMutable = listHelper.getMutable();
|
||||
this.unidirectional = listHelper.getUnidirectional();
|
||||
this.propertiesOne = listHelper.getOnes();
|
||||
this.propertiesOneExported = listHelper.getOneExported();
|
||||
@@ -430,14 +442,46 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
if (Modifier.isAbstract(beanType.getModifiers())) {
|
||||
this.idPropertyIndex = -1;
|
||||
this.versionPropertyIndex = -1;
|
||||
this.unloadProperties = new int[0];
|
||||
|
||||
} else {
|
||||
EntityBeanIntercept ebi = prototypeEntityBean._ebean_getIntercept();
|
||||
this.idPropertyIndex = (idProperty == null) ? -1 : ebi.findProperty(idProperty.getName());
|
||||
this.versionPropertyIndex = (versionProperty == null) ? -1 : ebi.findProperty(versionProperty.getName());
|
||||
this.unloadProperties = derivePropertiesToUnload(prototypeEntityBean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive an array of property positions for properties that are initialised in the constructor.
|
||||
* These properties need to be unloaded when populating beans for queries.
|
||||
*/
|
||||
private int[] derivePropertiesToUnload(EntityBean prototypeEntityBean) {
|
||||
|
||||
boolean[] loaded = prototypeEntityBean._ebean_getIntercept().getLoaded();
|
||||
int[] props = new int[loaded.length];
|
||||
int pos = 0;
|
||||
|
||||
// collect the positions of the properties initialised in the default constructor.
|
||||
for (int i = 0; i < loaded.length; i++) {
|
||||
if (loaded[i]) {
|
||||
props[pos++] = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos == 0) {
|
||||
// nothing set in the constructor
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
// populate a smaller/minimal array
|
||||
int[] unload = new int[pos];
|
||||
for (int i = 0; i < pos; i++) {
|
||||
unload[i] = props[i];
|
||||
}
|
||||
return unload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an entity bean that is used as a prototype/factory to create new instances.
|
||||
*/
|
||||
@@ -842,6 +886,20 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
cacheBeanPutData((EntityBean)bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the raw cache data from the bean.
|
||||
*/
|
||||
public CachedBeanData cacheBeanExtractData(EntityBean bean) {
|
||||
return cacheHelp.beanExtractData(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the raw cache data into the bean.
|
||||
*/
|
||||
public void cacheBeanLoadData(EntityBean bean, CachedBeanData data) {
|
||||
cacheHelp.beanLoadData(bean, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a bean into the bean cache.
|
||||
*/
|
||||
@@ -1128,7 +1186,17 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
*/
|
||||
public EntityBean createEntityBean() {
|
||||
try {
|
||||
return (EntityBean)prototypeEntityBean._ebean_newInstance();
|
||||
EntityBean bean = (EntityBean)prototypeEntityBean._ebean_newInstance();
|
||||
|
||||
if (unloadProperties.length > 0) {
|
||||
// 'unload' any properties initialised in the default constructor
|
||||
EntityBeanIntercept ebi = bean._ebean_getIntercept();
|
||||
for (int i = 0; i < unloadProperties.length; i++) {
|
||||
ebi.setPropertyUnloaded(unloadProperties[i]);
|
||||
}
|
||||
}
|
||||
return bean;
|
||||
|
||||
} catch (Exception ex) {
|
||||
throw new PersistenceException(ex);
|
||||
}
|
||||
@@ -1857,7 +1925,27 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for mutable scalar types and mark as dirty if necessary.
|
||||
*/
|
||||
public void checkMutableProperties(EntityBeanIntercept ebi) {
|
||||
for (int i = 0; i < propertiesMutable.length; i++) {
|
||||
BeanProperty beanProperty = propertiesMutable[i];
|
||||
if (ebi.isDirtyProperty(beanProperty.getPropertyIndex())) {
|
||||
// already marked as dirty
|
||||
} else if (ebi.isLoadedProperty(beanProperty.getPropertyIndex())) {
|
||||
Object value = beanProperty.getValue(ebi.getOwner());
|
||||
if (value == null || beanProperty.isDirtyValue(value)) {
|
||||
// mutable scalar value which is considered dirty so mark
|
||||
// it as such so that it is included in an update
|
||||
ebi.markPropertyAsChanged(beanProperty.getPropertyIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ConcurrencyMode getConcurrencyMode(EntityBeanIntercept ebi) {
|
||||
|
||||
if (!hasVersionProperty(ebi)) {
|
||||
return ConcurrencyMode.NONE;
|
||||
} else {
|
||||
@@ -2193,12 +2281,5 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
|
||||
return ctx.popBeanState();
|
||||
}
|
||||
|
||||
public void flushPersistenceContextOnIterate(PersistenceContext persistenceContext) {
|
||||
persistenceContext.clear(beanType);
|
||||
for (int i = 0; i < propertiesMany.length; i++) {
|
||||
persistenceContext.clear(propertiesMany[i].getBeanDescriptor().getBeanType());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import com.avaje.ebeaninternal.server.cache.CachedBeanDataUpdate;
|
||||
import com.avaje.ebeaninternal.server.cache.CachedManyIds;
|
||||
import com.avaje.ebeaninternal.server.core.CacheOptions;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
|
||||
import com.avaje.ebeaninternal.server.loadcontext.DLoadContext;
|
||||
import com.avaje.ebeaninternal.server.querydefn.NaturalKeyBindParam;
|
||||
import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
|
||||
|
||||
@@ -328,14 +327,15 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
if (context == null) {
|
||||
context = new DefaultPersistenceContext();
|
||||
}
|
||||
context.put(query.getId(), bean);
|
||||
|
||||
DLoadContext loadContext = new DLoadContext(desc.getEbeanServer(), desc, query.isReadOnly(), query);
|
||||
loadContext.setPersistenceContext(context);
|
||||
|
||||
EntityBeanIntercept ebi = ((EntityBean) bean)._ebean_getIntercept();
|
||||
// Not using a loadContext for beans coming out of L2 cache
|
||||
// so that means no batch lazy loading for these beans
|
||||
EntityBean entityBean = (EntityBean)bean;
|
||||
EntityBeanIntercept ebi = entityBean._ebean_getIntercept();
|
||||
ebi.setPersistenceContext(context);
|
||||
loadContext.register(null, ebi);
|
||||
Object id = desc.getId(entityBean);
|
||||
context.put(id, bean);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -360,12 +360,20 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public CachedBeanData beanExtractData(EntityBean bean) {
|
||||
return CachedBeanDataFromBean.extract(desc, bean);
|
||||
}
|
||||
|
||||
public void beanLoadData(EntityBean bean, CachedBeanData data) {
|
||||
CachedBeanDataToBean.load(desc, bean, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a bean into the bean cache.
|
||||
*/
|
||||
public void beanCachePut(EntityBean bean) {
|
||||
|
||||
CachedBeanData beanData = CachedBeanDataFromBean.extract(desc, bean);
|
||||
CachedBeanData beanData = beanExtractData(bean);
|
||||
|
||||
Object id = desc.getId(bean);
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
@@ -402,15 +410,15 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
@SuppressWarnings("unchecked")
|
||||
private T beanCacheGetInternal(Object id, Boolean readOnly) {
|
||||
|
||||
CachedBeanData d = (CachedBeanData) getBeanCache().get(id);
|
||||
if (d == null) {
|
||||
CachedBeanData data = (CachedBeanData) getBeanCache().get(id);
|
||||
if (data == null) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" GET {}({}) - cache miss", cacheName, id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (cacheSharableBeans && !Boolean.FALSE.equals(readOnly)) {
|
||||
Object bean = d.getSharableBean();
|
||||
Object bean = data.getSharableBean();
|
||||
if (bean != null) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" GET {}({}) - hit shared bean", cacheName, id);
|
||||
@@ -425,7 +433,7 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
bean._ebean_getIntercept().setReadOnly(true);
|
||||
}
|
||||
|
||||
CachedBeanDataToBean.load(desc, bean, d);
|
||||
beanLoadData(bean, data);
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" GET {}({}) - hit", cacheName, id);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.Model;
|
||||
import com.avaje.ebean.RawSql;
|
||||
import com.avaje.ebean.RawSqlBuilder;
|
||||
import com.avaje.ebean.annotation.ConcurrencyMode;
|
||||
@@ -149,6 +150,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
|
||||
private final XmlConfig xmlConfig;
|
||||
|
||||
private final BeanLifecycleAdapterFactory beanLifecycleAdapterFactory;
|
||||
|
||||
/**
|
||||
* Create for a given database dbConfig.
|
||||
*/
|
||||
@@ -177,6 +180,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
|
||||
this.updateChangesOnly = config.getServerConfig().isUpdateChangesOnly();
|
||||
|
||||
this.beanLifecycleAdapterFactory = new BeanLifecycleAdapterFactory();
|
||||
this.persistControllerManager = new PersistControllerManager(bootupClasses);
|
||||
this.persistListenerManager = new PersistListenerManager(bootupClasses);
|
||||
this.beanQueryAdapterManager = new BeanQueryAdapterManager(bootupClasses);
|
||||
@@ -1010,6 +1014,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
|
||||
desc.setUpdateChangesOnly(updateChangesOnly);
|
||||
|
||||
beanLifecycleAdapterFactory.addLifecycleMethods(desc);
|
||||
|
||||
// set bean controller, finder and listener
|
||||
setBeanControllerFinderListener(desc);
|
||||
deplyInherit.process(desc);
|
||||
@@ -1406,6 +1412,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
// we got to the top of the inheritance
|
||||
return;
|
||||
}
|
||||
if (Model.class.equals(superclass)) {
|
||||
// top of the inheritance. Not enhancing Model at this stage
|
||||
return;
|
||||
}
|
||||
if (!EntityBean.class.isAssignableFrom(superclass)) {
|
||||
throw new IllegalStateException("Super type "+superclass+" is not enhanced?");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
package com.avaje.ebeaninternal.server.deploy;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.PostLoad;
|
||||
import javax.persistence.PostPersist;
|
||||
import javax.persistence.PostRemove;
|
||||
import javax.persistence.PostUpdate;
|
||||
import javax.persistence.PrePersist;
|
||||
import javax.persistence.PreRemove;
|
||||
import javax.persistence.PreUpdate;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.avaje.ebean.event.BeanPersistAdapter;
|
||||
import com.avaje.ebean.event.BeanPersistRequest;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
|
||||
/**
|
||||
* Helper that looks for methods annotated with lifecycle events and registers an adapter for them.
|
||||
* <p>
|
||||
* This includes PrePerist, PostPerist, PreUpdate, PostUpdate, PreRemove, PostRemove and PostLoad
|
||||
* lifecycle events.
|
||||
* </p>
|
||||
*/
|
||||
public class BeanLifecycleAdapterFactory {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BeanLifecycleAdapterFactory.class);
|
||||
|
||||
/**
|
||||
* Register a BeanPersistController for methods annotated with lifecycle events.
|
||||
*/
|
||||
public void addLifecycleMethods(DeployBeanDescriptor<?> deployDesc) {
|
||||
|
||||
Method[] methods = deployDesc.getBeanType().getMethods();
|
||||
|
||||
MethodHolder methodHolder = new MethodHolder();
|
||||
|
||||
for (Method m : methods) {
|
||||
methodHolder.checkMethod(m);
|
||||
}
|
||||
|
||||
if (methodHolder.hasListener()) {
|
||||
deployDesc.addPersistController(new Adapter(methodHolder));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds Methods for the lifecycle events.s
|
||||
*/
|
||||
private static class MethodHolder {
|
||||
|
||||
private boolean hasListener;
|
||||
private Method preInsert;
|
||||
private Method postInsert;
|
||||
private Method preUpdate;
|
||||
private Method postUpdate;
|
||||
private Method preDelete;
|
||||
private Method postDelete;
|
||||
private Method postLoad;
|
||||
|
||||
private boolean hasListener() {
|
||||
return hasListener;
|
||||
}
|
||||
|
||||
private void checkMethod(Method method) {
|
||||
if (method.isAnnotationPresent(PrePersist.class)) {
|
||||
preInsert = method;
|
||||
hasListener = true;
|
||||
}
|
||||
if (method.isAnnotationPresent(PostPersist.class)) {
|
||||
postInsert = method;
|
||||
hasListener = true;
|
||||
}
|
||||
|
||||
if (method.isAnnotationPresent(PreUpdate.class)) {
|
||||
preUpdate = method;
|
||||
hasListener = true;
|
||||
}
|
||||
if (method.isAnnotationPresent(PostUpdate.class)) {
|
||||
postUpdate = method;
|
||||
hasListener = true;
|
||||
}
|
||||
|
||||
if (method.isAnnotationPresent(PreRemove.class)) {
|
||||
preDelete = method;
|
||||
hasListener = true;
|
||||
}
|
||||
if (method.isAnnotationPresent(PostRemove.class)) {
|
||||
postDelete = method;
|
||||
hasListener = true;
|
||||
}
|
||||
|
||||
if (method.isAnnotationPresent(PostLoad.class)) {
|
||||
postLoad = method;
|
||||
hasListener = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BeanPersistAdapter using reflection to invoke lifecycle methods.
|
||||
*/
|
||||
private static class Adapter extends BeanPersistAdapter {
|
||||
|
||||
private final MethodHolder methodHolder;
|
||||
|
||||
private Adapter(MethodHolder methodHolder) {
|
||||
this.methodHolder = methodHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegisterFor(Class<?> cls) {
|
||||
// Not used
|
||||
return false;
|
||||
}
|
||||
|
||||
private void invoke(Method method, Object bean) {
|
||||
try {
|
||||
method.invoke(bean);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error invoking lifecycle adapter", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void invoke(Method method, BeanPersistRequest<?> request) {
|
||||
invoke(method, request.getBean());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preDelete(BeanPersistRequest<?> request) {
|
||||
if (methodHolder.preDelete != null) {
|
||||
invoke(methodHolder.preDelete, request);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preInsert(BeanPersistRequest<?> request) {
|
||||
if (methodHolder.preInsert != null) {
|
||||
invoke(methodHolder.preInsert, request);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preUpdate(BeanPersistRequest<?> request) {
|
||||
if (methodHolder.preUpdate != null) {
|
||||
invoke(methodHolder.preUpdate, request);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDelete(BeanPersistRequest<?> request) {
|
||||
if (methodHolder.postDelete != null) {
|
||||
invoke(methodHolder.postDelete, request);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInsert(BeanPersistRequest<?> request) {
|
||||
if (methodHolder.postInsert != null) {
|
||||
invoke(methodHolder.postInsert, request);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postUpdate(BeanPersistRequest<?> request) {
|
||||
if (methodHolder.postUpdate != null) {
|
||||
invoke(methodHolder.postUpdate, request);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postLoad(Object bean, Set<String> includedProperties) {
|
||||
if (methodHolder.postLoad != null) {
|
||||
invoke(methodHolder.postLoad, bean);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder;
|
||||
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
|
||||
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
|
||||
import com.avaje.ebeaninternal.server.query.SqlBeanLoad;
|
||||
import com.avaje.ebeaninternal.server.query.SqlJoinType;
|
||||
import com.avaje.ebeaninternal.server.reflect.BeanReflectGetter;
|
||||
import com.avaje.ebeaninternal.server.reflect.BeanReflectSetter;
|
||||
import com.avaje.ebeaninternal.server.text.json.ReadJsonContext;
|
||||
@@ -73,6 +74,11 @@ public class BeanProperty implements ElPropertyValue {
|
||||
*/
|
||||
final boolean unidirectionalShadow;
|
||||
|
||||
/**
|
||||
* Flag set if this maps to the inheritance discriminator column
|
||||
*/
|
||||
final boolean discriminator;
|
||||
|
||||
/**
|
||||
* Flag to mark the property as embedded. This could be on
|
||||
* BeanPropertyAssocOne rather than here. Put it here for checking Id type
|
||||
@@ -267,6 +273,7 @@ public class BeanProperty implements ElPropertyValue {
|
||||
this.propertyIndex = deploy.getPropertyIndex();
|
||||
|
||||
this.unidirectionalShadow = deploy.isUndirectionalShadow();
|
||||
this.discriminator = deploy.isDiscriminator();
|
||||
this.localEncrypted = deploy.isLocalEncrypted();
|
||||
this.dbEncrypted = deploy.isDbEncrypted();
|
||||
this.dbEncryptedType = deploy.getDbEncryptedType();
|
||||
@@ -305,12 +312,6 @@ public class BeanProperty implements ElPropertyValue {
|
||||
this.readMethod = deploy.getReadMethod();
|
||||
this.writeMethod = deploy.getWriteMethod();
|
||||
this.getter = deploy.getGetter();
|
||||
if (descriptor != null && getter == null) {
|
||||
if (!unidirectionalShadow) {
|
||||
String m = "Null Getter for: " + getFullBeanName();
|
||||
throw new RuntimeException(m);
|
||||
}
|
||||
}
|
||||
this.setter = deploy.getSetter();
|
||||
|
||||
this.dbColumn = tableAliasIntern(descriptor, deploy.getDbColumn(), false, null);
|
||||
@@ -368,6 +369,7 @@ public class BeanProperty implements ElPropertyValue {
|
||||
|
||||
this.fetchEager = source.fetchEager;
|
||||
this.unidirectionalShadow = source.unidirectionalShadow;
|
||||
this.discriminator = source.discriminator;
|
||||
this.localEncrypted = source.isLocalEncrypted();
|
||||
this.isTransient = source.isTransient();
|
||||
this.secondaryTable = source.isSecondaryTable();
|
||||
@@ -469,6 +471,23 @@ public class BeanProperty implements ElPropertyValue {
|
||||
return formula;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property maps to the inheritance discriminator column.
|
||||
*/
|
||||
public boolean isDiscriminator() {
|
||||
return discriminator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the underlying type is mutable.
|
||||
*/
|
||||
public boolean isMutableScalarType() {
|
||||
if (scalarType == null) {
|
||||
return false;
|
||||
}
|
||||
return scalarType.isMutable();
|
||||
}
|
||||
|
||||
public void copyProperty(EntityBean sourceBean, EntityBean destBean) {
|
||||
Object value = getValue(sourceBean);
|
||||
setValue(destBean, value);
|
||||
@@ -501,14 +520,14 @@ public class BeanProperty implements ElPropertyValue {
|
||||
* Add any extra joins required to support this property. Generally a no
|
||||
* operation except for a OneToOne exported.
|
||||
*/
|
||||
public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {
|
||||
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
|
||||
if (formula && sqlFormulaJoin != null) {
|
||||
ctx.appendFormulaJoin(sqlFormulaJoin, forceOuterJoin);
|
||||
ctx.appendFormulaJoin(sqlFormulaJoin, joinType);
|
||||
|
||||
} else if (secondaryTableJoin != null) {
|
||||
|
||||
String relativePrefix = ctx.getRelativePrefix(secondaryTableJoinPrefix);
|
||||
secondaryTableJoin.addJoin(forceOuterJoin, relativePrefix, ctx);
|
||||
secondaryTableJoin.addJoin(joinType, relativePrefix, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -859,6 +878,14 @@ public class BeanProperty implements ElPropertyValue {
|
||||
return descriptor.getFullName() + "." + name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the mutable value is considered dirty.
|
||||
* This is only used for 'mutable' scalar types like hstore etc.
|
||||
*/
|
||||
public boolean isDirtyValue(Object value) {
|
||||
return scalarType.isDirty(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the scalarType.
|
||||
*/
|
||||
@@ -977,6 +1004,13 @@ public class BeanProperty implements ElPropertyValue {
|
||||
return isTransient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property is loadable from a resultSet.
|
||||
*/
|
||||
public boolean isLoadProperty() {
|
||||
return !isTransient || formula;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a version column used for concurrency checking.
|
||||
*/
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.avaje.ebeaninternal.server.deploy.id.ImportedIdSimple;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
|
||||
import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder;
|
||||
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
|
||||
import com.avaje.ebeaninternal.server.query.SqlJoinType;
|
||||
|
||||
/**
|
||||
* Abstract base for properties mapped to an associated bean, list, set or map.
|
||||
@@ -129,22 +130,15 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
|
||||
/**
|
||||
* Add table join with table alias based on prefix.
|
||||
*/
|
||||
public boolean addJoin(boolean forceOuterJoin, String prefix, DbSqlContext ctx) {
|
||||
return tableJoin.addJoin(forceOuterJoin, prefix, ctx);
|
||||
public SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx) {
|
||||
return tableJoin.addJoin(joinType, prefix, ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add table join with explicit table alias.
|
||||
*/
|
||||
public boolean addJoin(boolean forceOuterJoin, String a1, String a2, DbSqlContext ctx) {
|
||||
return tableJoin.addJoin(forceOuterJoin, a1, a2, ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add table join with explicit table alias.
|
||||
*/
|
||||
public void addInnerJoin(String a1, String a2, DbSqlContext ctx) {
|
||||
tableJoin.addInnerJoin(a1, a2, ctx);
|
||||
public SqlJoinType addJoin(SqlJoinType joinType, String a1, String a2, DbSqlContext ctx) {
|
||||
return tableJoin.addJoin(joinType, a1, a2, ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -60,8 +60,16 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
*/
|
||||
private final boolean manyToMany;
|
||||
|
||||
/**
|
||||
* Order by used when fetch joining the associated many.
|
||||
*/
|
||||
private final String fetchOrderBy;
|
||||
|
||||
/**
|
||||
* Order by used when lazy loading the associated many.
|
||||
*/
|
||||
private String lazyFetchOrderBy;
|
||||
|
||||
private final String mapKey;
|
||||
|
||||
/**
|
||||
@@ -137,6 +145,22 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
if (exportedProperties.length > 0){
|
||||
embeddedExportedProperties = exportedProperties[0].isEmbedded();
|
||||
exportedPropertyBindProto = deriveExportedPropertyBindProto();
|
||||
|
||||
if (fetchOrderBy != null) {
|
||||
// derive lazyFetchOrderBy
|
||||
StringBuilder sb = new StringBuilder(50);
|
||||
for (int i = 0; i < exportedProperties.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
// these fkcolumns always on base table hence t0 as alias
|
||||
sb.append("t0.").append(exportedProperties[i].getForeignDbColumn());
|
||||
}
|
||||
if (fetchOrderBy != null) {
|
||||
sb.append(", ").append(fetchOrderBy);
|
||||
}
|
||||
lazyFetchOrderBy = sb.toString().trim();
|
||||
}
|
||||
}
|
||||
|
||||
String delStmt;
|
||||
@@ -510,6 +534,13 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the order by for use when lazy loading the associated collection.
|
||||
*/
|
||||
public String getLazyFetchOrderBy() {
|
||||
return lazyFetchOrderBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default mapKey when returning a Map.
|
||||
*/
|
||||
public String getMapKey() {
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.avaje.ebean.Transaction;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.bean.EntityBeanIntercept;
|
||||
import com.avaje.ebean.bean.PersistenceContext;
|
||||
import com.avaje.ebeaninternal.server.cache.CachedBeanData;
|
||||
import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate;
|
||||
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
|
||||
import com.avaje.ebeaninternal.server.deploy.id.ImportedId;
|
||||
@@ -22,6 +23,7 @@ import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder;
|
||||
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
|
||||
import com.avaje.ebeaninternal.server.query.SplitName;
|
||||
import com.avaje.ebeaninternal.server.query.SqlBeanLoad;
|
||||
import com.avaje.ebeaninternal.server.query.SqlJoinType;
|
||||
import com.avaje.ebeaninternal.server.text.json.ReadJsonContext;
|
||||
import com.avaje.ebeaninternal.server.text.json.WriteJsonContext;
|
||||
|
||||
@@ -323,15 +325,15 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
|
||||
}
|
||||
|
||||
public Object getCacheDataValue(EntityBean bean){
|
||||
if (embedded) {
|
||||
throw new RuntimeException();
|
||||
Object ap = getValue(bean);
|
||||
if (ap == null){
|
||||
return null;
|
||||
}
|
||||
if (embedded) {
|
||||
return targetDescriptor.cacheBeanExtractData((EntityBean) ap);
|
||||
|
||||
} else {
|
||||
Object ap = getValue(bean);
|
||||
if (ap == null){
|
||||
return null;
|
||||
} else {
|
||||
return targetDescriptor.getId((EntityBean)ap);
|
||||
}
|
||||
return targetDescriptor.getId((EntityBean)ap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,7 +341,10 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
|
||||
public void setCacheDataValue(EntityBean bean, Object cacheData){
|
||||
if (cacheData != null) {
|
||||
if (embedded){
|
||||
throw new RuntimeException();
|
||||
EntityBean embeddedBean = targetDescriptor.createEntityBean();
|
||||
targetDescriptor.cacheBeanLoadData(embeddedBean, (CachedBeanData) cacheData);
|
||||
setValue(bean, embeddedBean);
|
||||
|
||||
} else {
|
||||
T ref = targetDescriptor.createReference(Boolean.FALSE, cacheData);
|
||||
setValue(bean, ref);
|
||||
@@ -504,9 +509,9 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {
|
||||
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
|
||||
if (!isTransient) {
|
||||
localHelp.appendFrom(ctx, forceOuterJoin);
|
||||
localHelp.appendFrom(ctx, joinType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,7 +588,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
|
||||
|
||||
abstract void appendSelect(DbSqlContext ctx, boolean subQuery);
|
||||
|
||||
abstract void appendFrom(DbSqlContext ctx, boolean forceOuterJoin);
|
||||
abstract void appendFrom(DbSqlContext ctx, SqlJoinType joinType);
|
||||
|
||||
}
|
||||
|
||||
@@ -629,7 +634,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {
|
||||
void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -725,11 +730,11 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {
|
||||
void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
|
||||
if (targetInheritInfo != null) {
|
||||
// add join to support the discriminator column
|
||||
String relativePrefix = ctx.getRelativePrefix(name);
|
||||
tableJoin.addJoin(forceOuterJoin, relativePrefix, ctx);
|
||||
tableJoin.addJoin(joinType, relativePrefix, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -818,10 +823,10 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {
|
||||
void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
|
||||
|
||||
String relativePrefix = ctx.getRelativePrefix(getName());
|
||||
tableJoin.addJoin(forceOuterJoin, relativePrefix, ctx);
|
||||
tableJoin.addJoin(joinType, relativePrefix, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -862,4 +867,18 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
|
||||
EntityBean eb = (EntityBean)detailBean;
|
||||
return targetDescriptor.isReference(eb._ebean_getIntercept());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parent bean to the child bean if it has not already been set.
|
||||
*/
|
||||
public void setParentBeanToChild(EntityBean parent, EntityBean child) {
|
||||
|
||||
if (mappedBy != null) {
|
||||
BeanProperty beanProperty = targetDescriptor.getBeanProperty(mappedBy);
|
||||
if (beanProperty != null && beanProperty.getValue(child) == null) {
|
||||
// set the 'parent' bean to the 'child' bean
|
||||
beanProperty.setValue(child, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ public class DRawSqlSelect {
|
||||
sqlTree.setSummary(desc.getName());
|
||||
|
||||
LinkedHashSet<String> includedProps = new LinkedHashSet<String>();
|
||||
SqlTreeProperties selectProps = new SqlTreeProperties(desc);
|
||||
SqlTreeProperties selectProps = new SqlTreeProperties();
|
||||
|
||||
for (int i = 0; i < selectColumns.length; i++) {
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.avaje.ebeaninternal.server.deploy;
|
||||
|
||||
import com.avaje.ebeaninternal.server.query.SqlJoinType;
|
||||
|
||||
/**
|
||||
* Used to provide context during sql construction.
|
||||
*/
|
||||
@@ -72,7 +74,7 @@ public interface DbSqlContext {
|
||||
* Append a Sql Formula join. This converts the "${ta}" keyword to the current
|
||||
* table alias.
|
||||
*/
|
||||
public void appendFormulaJoin(String sqlFormulaJoin, boolean forceOuterJoin);
|
||||
public void appendFormulaJoin(String sqlFormulaJoin, SqlJoinType joinType);
|
||||
|
||||
/**
|
||||
* Return the current content length.
|
||||
|
||||
@@ -13,338 +13,332 @@ import com.avaje.ebeaninternal.server.deploy.parse.DeployInheritInfo;
|
||||
import com.avaje.ebeaninternal.server.query.SqlTreeProperties;
|
||||
|
||||
/**
|
||||
* Represents a node in the Inheritance tree. Holds information regarding Super
|
||||
* Subclass support.
|
||||
* Represents a node in the Inheritance tree. Holds information regarding Super Subclass support.
|
||||
*/
|
||||
public class InheritInfo {
|
||||
|
||||
private final String discriminatorStringValue;
|
||||
private final Object discriminatorValue;
|
||||
private final String discriminatorStringValue;
|
||||
private final Object discriminatorValue;
|
||||
|
||||
private final String discriminatorColumn;
|
||||
private final String discriminatorColumn;
|
||||
|
||||
private final int discriminatorType;
|
||||
private final int discriminatorType;
|
||||
|
||||
private final int discriminatorLength;
|
||||
private final int discriminatorLength;
|
||||
|
||||
private final String where;
|
||||
private final String where;
|
||||
|
||||
private final Class<?> type;
|
||||
private final Class<?> type;
|
||||
|
||||
private final ArrayList<InheritInfo> children = new ArrayList<InheritInfo>();
|
||||
private final ArrayList<InheritInfo> children = new ArrayList<InheritInfo>();
|
||||
|
||||
/**
|
||||
* Map of discriminator values to InheritInfo.
|
||||
*/
|
||||
private final HashMap<String, InheritInfo> discMap;
|
||||
|
||||
/**
|
||||
* Map of class types to InheritInfo (taking into account subclass proxy classes).
|
||||
*/
|
||||
private final HashMap<String, InheritInfo> typeMap;
|
||||
/**
|
||||
* Map of discriminator values to InheritInfo.
|
||||
*/
|
||||
private final HashMap<String, InheritInfo> discMap;
|
||||
|
||||
private final InheritInfo parent;
|
||||
/**
|
||||
* Map of class types to InheritInfo (taking into account subclass proxy classes).
|
||||
*/
|
||||
private final HashMap<String, InheritInfo> typeMap;
|
||||
|
||||
private final InheritInfo root;
|
||||
private final InheritInfo parent;
|
||||
|
||||
private BeanDescriptor<?> descriptor;
|
||||
private final InheritInfo root;
|
||||
|
||||
public InheritInfo(InheritInfo r, InheritInfo parent, DeployInheritInfo deploy) {
|
||||
|
||||
this.parent = parent;
|
||||
this.type = deploy.getType();
|
||||
this.discriminatorColumn = InternString.intern(deploy.getDiscriminatorColumn(parent));
|
||||
this.discriminatorValue = deploy.getDiscriminatorObjectValue();
|
||||
this.discriminatorStringValue = deploy.getDiscriminatorStringValue();
|
||||
|
||||
this.discriminatorType = deploy.getDiscriminatorType(parent);
|
||||
this.discriminatorLength = deploy.getDiscriminatorLength(parent);
|
||||
this.where = InternString.intern(deploy.getWhere());
|
||||
|
||||
if (r == null) {
|
||||
// this is a root node
|
||||
root = this;
|
||||
discMap = new HashMap<String, InheritInfo>();
|
||||
typeMap = new HashMap<String, InheritInfo>();
|
||||
registerWithRoot(this);
|
||||
private BeanDescriptor<?> descriptor;
|
||||
|
||||
} else {
|
||||
this.root = r;
|
||||
// register with the root node...
|
||||
discMap = null;
|
||||
typeMap = null;
|
||||
root.registerWithRoot(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit all the children in the inheritance tree.
|
||||
*/
|
||||
public void visitChildren(InheritInfoVisitor visitor) {
|
||||
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
InheritInfo child = children.get(i);
|
||||
visitor.visit(child);
|
||||
child.visitChildren(visitor);
|
||||
}
|
||||
}
|
||||
public InheritInfo(InheritInfo r, InheritInfo parent, DeployInheritInfo deploy) {
|
||||
|
||||
/**
|
||||
* return true if anything in the inheritance hierarchy has a relationship
|
||||
* with a save cascade on it.
|
||||
*/
|
||||
public boolean isSaveRecurseSkippable() {
|
||||
return root.isNodeSaveRecurseSkippable();
|
||||
}
|
||||
|
||||
private boolean isNodeSaveRecurseSkippable() {
|
||||
if (!descriptor.isSaveRecurseSkippable()){
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
InheritInfo child = children.get(i);
|
||||
if (!child.isNodeSaveRecurseSkippable()){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* return true if anything in the inheritance hierarchy has a relationship
|
||||
* with a delete cascade on it.
|
||||
*/
|
||||
public boolean isDeleteRecurseSkippable() {
|
||||
return root.isNodeDeleteRecurseSkippable();
|
||||
this.parent = parent;
|
||||
this.type = deploy.getType();
|
||||
this.discriminatorColumn = InternString.intern(deploy.getDiscriminatorColumn(parent));
|
||||
this.discriminatorValue = deploy.getDiscriminatorObjectValue();
|
||||
this.discriminatorStringValue = deploy.getDiscriminatorStringValue();
|
||||
|
||||
this.discriminatorType = deploy.getDiscriminatorType(parent);
|
||||
this.discriminatorLength = deploy.getDiscriminatorLength(parent);
|
||||
this.where = InternString.intern(deploy.getWhere());
|
||||
|
||||
if (r == null) {
|
||||
// this is a root node
|
||||
root = this;
|
||||
discMap = new HashMap<String, InheritInfo>();
|
||||
typeMap = new HashMap<String, InheritInfo>();
|
||||
registerWithRoot(this);
|
||||
|
||||
} else {
|
||||
this.root = r;
|
||||
// register with the root node...
|
||||
discMap = null;
|
||||
typeMap = null;
|
||||
root.registerWithRoot(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit all the children in the inheritance tree.
|
||||
*/
|
||||
public void visitChildren(InheritInfoVisitor visitor) {
|
||||
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
InheritInfo child = children.get(i);
|
||||
visitor.visit(child);
|
||||
child.visitChildren(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return true if anything in the inheritance hierarchy has a relationship with a save cascade on
|
||||
* it.
|
||||
*/
|
||||
public boolean isSaveRecurseSkippable() {
|
||||
return root.isNodeSaveRecurseSkippable();
|
||||
}
|
||||
|
||||
private boolean isNodeSaveRecurseSkippable() {
|
||||
if (!descriptor.isSaveRecurseSkippable()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
InheritInfo child = children.get(i);
|
||||
if (!child.isNodeSaveRecurseSkippable()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* return true if anything in the inheritance hierarchy has a relationship with a delete cascade
|
||||
* on it.
|
||||
*/
|
||||
public boolean isDeleteRecurseSkippable() {
|
||||
return root.isNodeDeleteRecurseSkippable();
|
||||
}
|
||||
|
||||
private boolean isNodeDeleteRecurseSkippable() {
|
||||
if (!descriptor.isDeleteRecurseSkippable()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
InheritInfo child = children.get(i);
|
||||
if (!child.isNodeDeleteRecurseSkippable()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the descriptor for this node.
|
||||
*/
|
||||
public void setDescriptor(BeanDescriptor<?> descriptor) {
|
||||
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associated BeanDescriptor for this node.
|
||||
*/
|
||||
public BeanDescriptor<?> getBeanDescriptor() {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bean property additionally looking in the sub types.
|
||||
*/
|
||||
public BeanProperty findSubTypeProperty(String propertyName) {
|
||||
|
||||
BeanProperty prop = null;
|
||||
|
||||
for (int i = 0, x = children.size(); i < x; i++) {
|
||||
InheritInfo childInfo = children.get(i);
|
||||
|
||||
// recursively search this child bean descriptor
|
||||
prop = childInfo.getBeanDescriptor().findBeanProperty(propertyName);
|
||||
|
||||
if (prop != null) {
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNodeDeleteRecurseSkippable() {
|
||||
if (!descriptor.isDeleteRecurseSkippable()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
InheritInfo child = children.get(i);
|
||||
if (!child.isNodeDeleteRecurseSkippable()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the local properties for each sub class below this one.
|
||||
*/
|
||||
public void addChildrenProperties(SqlTreeProperties selectProps) {
|
||||
|
||||
for (int i = 0, x = children.size(); i < x; i++) {
|
||||
InheritInfo childInfo = children.get(i);
|
||||
selectProps.add(childInfo.descriptor.propertiesLocal());
|
||||
|
||||
childInfo.addChildrenProperties(selectProps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the descriptor for this node.
|
||||
*/
|
||||
public void setDescriptor(BeanDescriptor<?> descriptor) {
|
||||
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associated BeanDescriptor for this node.
|
||||
*/
|
||||
public BeanDescriptor<?> getBeanDescriptor() {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bean property additionally looking in the sub types.
|
||||
*/
|
||||
public BeanProperty findSubTypeProperty(String propertyName) {
|
||||
|
||||
BeanProperty prop = null;
|
||||
|
||||
for (int i = 0, x=children.size(); i < x; i++) {
|
||||
InheritInfo childInfo = children.get(i);
|
||||
|
||||
// recursively search this child bean descriptor
|
||||
prop = childInfo.getBeanDescriptor().findBeanProperty(propertyName);
|
||||
|
||||
if (prop != null){
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the local properties for each sub class below this one.
|
||||
*/
|
||||
public void addChildrenProperties(SqlTreeProperties selectProps) {
|
||||
|
||||
for (int i = 0, x=children.size(); i < x; i++) {
|
||||
InheritInfo childInfo = children.get(i);
|
||||
selectProps.add(childInfo.descriptor.propertiesLocal());
|
||||
|
||||
childInfo.addChildrenProperties(selectProps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associated InheritInfo for this DB row read.
|
||||
*/
|
||||
public InheritInfo readType(DbReadContext ctx) throws SQLException {
|
||||
/**
|
||||
* Return the associated InheritInfo for this DB row read.
|
||||
*/
|
||||
public InheritInfo readType(DbReadContext ctx) throws SQLException {
|
||||
|
||||
String discValue = ctx.getDataReader().getString();
|
||||
return readType(discValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associated InheritInfo for this discriminator value.
|
||||
*/
|
||||
public InheritInfo readType(String discValue) {
|
||||
String discValue = ctx.getDataReader().getString();
|
||||
return readType(discValue);
|
||||
}
|
||||
|
||||
if (discValue == null) {
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Return the associated InheritInfo for this discriminator value.
|
||||
*/
|
||||
public InheritInfo readType(String discValue) {
|
||||
|
||||
InheritInfo typeInfo = root.getType(discValue);
|
||||
if (typeInfo == null) {
|
||||
String m = "Inheritance type for discriminator value [" + discValue + "] was not found?";
|
||||
throw new PersistenceException(m);
|
||||
}
|
||||
|
||||
return typeInfo;
|
||||
if (discValue == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associated InheritInfo for this bean type.
|
||||
*/
|
||||
public InheritInfo readType(Class<?> beanType) {
|
||||
|
||||
InheritInfo typeInfo = root.getTypeByClass(beanType);
|
||||
if (typeInfo == null) {
|
||||
String m = "Inheritance type for bean type [" + beanType.getName() + "] was not found?";
|
||||
throw new PersistenceException(m);
|
||||
}
|
||||
|
||||
return typeInfo;
|
||||
InheritInfo typeInfo = root.getType(discValue);
|
||||
if (typeInfo == null) {
|
||||
throw new PersistenceException("Inheritance type for discriminator value [" + discValue + "] was not found?");
|
||||
}
|
||||
return typeInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an EntityBean for this type.
|
||||
*/
|
||||
public EntityBean createEntityBean() {
|
||||
return descriptor.createEntityBean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the IdBinder for this type.
|
||||
*/
|
||||
public IdBinder getIdBinder() {
|
||||
return descriptor.getIdBinder();
|
||||
}
|
||||
|
||||
/**
|
||||
* return the type.
|
||||
*/
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
/**
|
||||
* Return the associated InheritInfo for this bean type.
|
||||
*/
|
||||
public InheritInfo readType(Class<?> beanType) {
|
||||
|
||||
/**
|
||||
* Return the root node of the tree.
|
||||
* <p>
|
||||
* The root has a map of discriminator values to types.
|
||||
* </p>
|
||||
*/
|
||||
public InheritInfo getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parent node.
|
||||
*/
|
||||
public InheritInfo getParent() {
|
||||
return parent;
|
||||
InheritInfo typeInfo = root.getTypeByClass(beanType);
|
||||
if (typeInfo == null) {
|
||||
throw new PersistenceException("Inheritance type for bean type [" + beanType.getName() + "] was not found?");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is abstract node.
|
||||
*/
|
||||
public boolean isAbstract() {
|
||||
return (discriminatorValue == null);
|
||||
}
|
||||
return typeInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is the root node.
|
||||
*/
|
||||
public boolean isRoot() {
|
||||
return parent == null;
|
||||
}
|
||||
/**
|
||||
* Create an EntityBean for this type.
|
||||
*/
|
||||
public EntityBean createEntityBean() {
|
||||
return descriptor.createEntityBean();
|
||||
}
|
||||
|
||||
/**
|
||||
* For a discriminator get the inheritance information for this tree.
|
||||
*/
|
||||
public InheritInfo getType(String discValue) {
|
||||
return discMap.get(discValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the InheritInfo for the given bean type.
|
||||
*/
|
||||
private InheritInfo getTypeByClass(Class<?> beanType) {
|
||||
return typeMap.get(beanType.getName());
|
||||
/**
|
||||
* Return the IdBinder for this type.
|
||||
*/
|
||||
public IdBinder getIdBinder() {
|
||||
return descriptor.getIdBinder();
|
||||
}
|
||||
|
||||
/**
|
||||
* return the type.
|
||||
*/
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the root node of the tree.
|
||||
* <p>
|
||||
* The root has a map of discriminator values to types.
|
||||
* </p>
|
||||
*/
|
||||
public InheritInfo getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parent node.
|
||||
*/
|
||||
public InheritInfo getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is abstract node.
|
||||
*/
|
||||
public boolean isAbstract() {
|
||||
return (discriminatorValue == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is the root node.
|
||||
*/
|
||||
public boolean isRoot() {
|
||||
return parent == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a discriminator get the inheritance information for this tree.
|
||||
*/
|
||||
public InheritInfo getType(String discValue) {
|
||||
return discMap.get(discValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the InheritInfo for the given bean type.
|
||||
*/
|
||||
private InheritInfo getTypeByClass(Class<?> beanType) {
|
||||
return typeMap.get(beanType.getName());
|
||||
}
|
||||
|
||||
private void registerWithRoot(InheritInfo info) {
|
||||
if (info.getDiscriminatorStringValue() != null) {
|
||||
String stringDiscValue = info.getDiscriminatorStringValue();
|
||||
discMap.put(stringDiscValue, info);
|
||||
}
|
||||
typeMap.put(info.getType().getName(), info);
|
||||
}
|
||||
|
||||
private void registerWithRoot(InheritInfo info) {
|
||||
if (info.getDiscriminatorStringValue() != null) {
|
||||
String stringDiscValue = info.getDiscriminatorStringValue();
|
||||
discMap.put(stringDiscValue, info);
|
||||
}
|
||||
typeMap.put(info.getType().getName(), info);
|
||||
}
|
||||
/**
|
||||
* Add a child node.
|
||||
*/
|
||||
public void addChild(InheritInfo childInfo) {
|
||||
children.add(childInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a child node.
|
||||
*/
|
||||
public void addChild(InheritInfo childInfo) {
|
||||
children.add(childInfo);
|
||||
}
|
||||
/**
|
||||
* Return the derived where for the discriminator.
|
||||
*/
|
||||
public String getWhere() {
|
||||
|
||||
/**
|
||||
* Return the derived where for the discriminator.
|
||||
*/
|
||||
public String getWhere() {
|
||||
return where;
|
||||
}
|
||||
|
||||
return where;
|
||||
}
|
||||
/**
|
||||
* Return the column name of the discriminator.
|
||||
*/
|
||||
public String getDiscriminatorColumn() {
|
||||
return discriminatorColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column name of the discriminator.
|
||||
*/
|
||||
public String getDiscriminatorColumn() {
|
||||
return discriminatorColumn;
|
||||
}
|
||||
/**
|
||||
* Return the sql type of the discriminator value.
|
||||
*/
|
||||
public int getDiscriminatorType() {
|
||||
return discriminatorType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sql type of the discriminator value.
|
||||
*/
|
||||
public int getDiscriminatorType() {
|
||||
return discriminatorType;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the length of the discriminator column.
|
||||
*/
|
||||
public int getDiscriminatorLength() {
|
||||
return discriminatorLength;
|
||||
}
|
||||
/**
|
||||
* Return the length of the discriminator column.
|
||||
*/
|
||||
public int getDiscriminatorLength() {
|
||||
return discriminatorLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the discriminator value for this node.
|
||||
*/
|
||||
public String getDiscriminatorStringValue() {
|
||||
return discriminatorStringValue;
|
||||
}
|
||||
/**
|
||||
* Return the discriminator value for this node.
|
||||
*/
|
||||
public String getDiscriminatorStringValue() {
|
||||
return discriminatorStringValue;
|
||||
}
|
||||
|
||||
public Object getDiscriminatorValue() {
|
||||
return discriminatorValue;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "InheritInfo[" + type.getName() + "] disc[" + discriminatorStringValue + "]";
|
||||
}
|
||||
public Object getDiscriminatorValue() {
|
||||
return discriminatorValue;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "InheritInfo[" + type.getName() + "] disc[" + discriminatorStringValue + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoin;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoinColumn;
|
||||
import com.avaje.ebeaninternal.server.query.SplitName;
|
||||
import com.avaje.ebeaninternal.server.query.SqlBeanLoad;
|
||||
import com.avaje.ebeaninternal.server.query.SqlJoinType;
|
||||
|
||||
/**
|
||||
* Represents a join to another table.
|
||||
@@ -32,9 +33,9 @@ public final class TableJoin {
|
||||
private final String table;
|
||||
|
||||
/**
|
||||
* The type of join. LEFT OUTER etc.
|
||||
* The type of join as per deployment (cardinality and optionality).
|
||||
*/
|
||||
private final String type;
|
||||
private final SqlJoinType type;
|
||||
|
||||
/**
|
||||
* The persist cascade info.
|
||||
@@ -60,7 +61,7 @@ public final class TableJoin {
|
||||
|
||||
this.importedPrimaryKey = deploy.isImportedPrimaryKey();
|
||||
this.table = InternString.intern(deploy.getTable());
|
||||
this.type = InternString.intern(deploy.getType());
|
||||
this.type = deploy.getType();
|
||||
this.cascadeInfo = deploy.getCascadeInfo();
|
||||
this.inheritInfo = deploy.getInheritInfo();
|
||||
|
||||
@@ -149,7 +150,7 @@ public final class TableJoin {
|
||||
/**
|
||||
* Return the type of join. LEFT OUTER JOIN etc.
|
||||
*/
|
||||
public String getType() {
|
||||
public SqlJoinType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -157,32 +158,26 @@ public final class TableJoin {
|
||||
* Return true if this join is a left outer join.
|
||||
*/
|
||||
public boolean isOuterJoin() {
|
||||
return type.equals(LEFT_OUTER);
|
||||
return type == SqlJoinType.OUTER;
|
||||
}
|
||||
|
||||
public boolean addJoin(boolean forceOuterJoin, String prefix, DbSqlContext ctx) {
|
||||
public SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx) {
|
||||
|
||||
String[] names = SplitName.split(prefix);
|
||||
String a1 = ctx.getTableAlias(names[0]);
|
||||
String a2 = ctx.getTableAlias(prefix);
|
||||
|
||||
return addJoin(forceOuterJoin, a1, a2, ctx);
|
||||
return addJoin(joinType, a1, a2, ctx);
|
||||
}
|
||||
|
||||
public boolean addJoin(boolean forceOuterJoin, String a1, String a2, DbSqlContext ctx) {
|
||||
public SqlJoinType addJoin(SqlJoinType joinType, String a1, String a2, DbSqlContext ctx) {
|
||||
|
||||
String inheritance = inheritInfo != null ? inheritInfo.getWhere() : null;
|
||||
|
||||
ctx.addJoin(forceOuterJoin?LEFT_OUTER:type, table, columns(), a1, a2, inheritance);
|
||||
String joinLiteral = joinType.getLiteral(type);
|
||||
ctx.addJoin(joinLiteral, table, columns(), a1, a2, inheritance);
|
||||
|
||||
return forceOuterJoin || LEFT_OUTER.equals(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly add a (non-outer) join.
|
||||
*/
|
||||
public void addInnerJoin(String a1, String a2, DbSqlContext ctx) {
|
||||
String inheritance = inheritInfo != null ? inheritInfo.getWhere() : null;
|
||||
ctx.addJoin(JOIN, table, columns(), a1, a2, inheritance);
|
||||
return joinType.autoToOuter(type);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -133,9 +133,9 @@ public class DeployBeanDescriptor<T> {
|
||||
*/
|
||||
private Class<T> beanType;
|
||||
|
||||
private List<BeanPersistController> persistControllers = new ArrayList<BeanPersistController>();
|
||||
private List<BeanPersistListener<T>> persistListeners = new ArrayList<BeanPersistListener<T>>();
|
||||
private List<BeanQueryAdapter> queryAdapters = new ArrayList<BeanQueryAdapter>();
|
||||
private List<BeanPersistController> persistControllers = new ArrayList<BeanPersistController>(2);
|
||||
private List<BeanPersistListener<T>> persistListeners = new ArrayList<BeanPersistListener<T>>(2);
|
||||
private List<BeanQueryAdapter> queryAdapters = new ArrayList<BeanQueryAdapter>(2);
|
||||
|
||||
private CacheOptions cacheOptions = new CacheOptions();
|
||||
|
||||
@@ -147,7 +147,7 @@ public class DeployBeanDescriptor<T> {
|
||||
/**
|
||||
* The table joins for this bean. Server side only.
|
||||
*/
|
||||
private ArrayList<DeployTableJoin> tableJoinList = new ArrayList<DeployTableJoin>();
|
||||
private ArrayList<DeployTableJoin> tableJoinList = new ArrayList<DeployTableJoin>(2);
|
||||
|
||||
/**
|
||||
* Inheritance information. Server side only.
|
||||
|
||||
@@ -86,6 +86,8 @@ public class DeployBeanProperty {
|
||||
|
||||
private boolean unique;
|
||||
|
||||
private boolean discriminator;
|
||||
|
||||
/**
|
||||
* The length or precision of the DB column.
|
||||
*/
|
||||
@@ -336,6 +338,20 @@ public class DeployBeanProperty {
|
||||
this.undirectionalShadow = undirectionalShadow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this property as mapping to the discriminator column.
|
||||
*/
|
||||
public void setDiscriminator(boolean discriminator) {
|
||||
this.discriminator = discriminator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property maps to the inheritance discriminator column.s
|
||||
*/
|
||||
public boolean isDiscriminator() {
|
||||
return discriminator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the property is encrypted in java rather than in the DB.
|
||||
*/
|
||||
|
||||
+28
-1
@@ -16,7 +16,9 @@ import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanPropertySimpleCollection;
|
||||
import com.avaje.ebeaninternal.server.deploy.InheritInfo;
|
||||
import com.avaje.ebeaninternal.server.deploy.TableJoin;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarTypeString;
|
||||
|
||||
/**
|
||||
* Helper object to classify BeanProperties into appropriate lists.
|
||||
@@ -35,6 +37,8 @@ public class DeployBeanPropertyLists {
|
||||
|
||||
private final ArrayList<BeanProperty> local = new ArrayList<BeanProperty>();
|
||||
|
||||
private final ArrayList<BeanProperty> mutable = new ArrayList<BeanProperty>();
|
||||
|
||||
private final ArrayList<BeanProperty> manys = new ArrayList<BeanProperty>();
|
||||
|
||||
private final ArrayList<BeanProperty> nonManys = new ArrayList<BeanProperty>();
|
||||
@@ -88,6 +92,21 @@ public class DeployBeanPropertyLists {
|
||||
allocateToList(prop);
|
||||
}
|
||||
|
||||
InheritInfo inheritInfo = deploy.getInheritInfo();
|
||||
if (inheritInfo != null) {
|
||||
// Create a BeanProperty for the discriminator column to support
|
||||
// using RawSql queries with inheritance
|
||||
String discriminatorColumn = inheritInfo.getDiscriminatorColumn();
|
||||
DeployBeanProperty discDeployProp = new DeployBeanProperty(deploy, String.class, new ScalarTypeString(), null);
|
||||
discDeployProp.setDiscriminator(true);
|
||||
discDeployProp.setName(discriminatorColumn);
|
||||
discDeployProp.setDbColumn(discriminatorColumn);
|
||||
|
||||
// create the discriminator BeanProperty and only register it in the propertyMap
|
||||
BeanProperty dprop = new BeanProperty(owner, desc, discDeployProp);
|
||||
propertyMap.put(dprop.getName(), dprop);
|
||||
}
|
||||
|
||||
List<DeployTableJoin> deployTableJoins = deploy.getTableJoins();
|
||||
tableJoins = new TableJoin[deployTableJoins.size()];
|
||||
for (int i = 0; i < deployTableJoins.size(); i++) {
|
||||
@@ -118,6 +137,10 @@ public class DeployBeanPropertyLists {
|
||||
nonTransients.add(prop);
|
||||
}
|
||||
|
||||
if (prop.isMutableScalarType()) {
|
||||
mutable.add(prop);
|
||||
}
|
||||
|
||||
if (desc.getInheritInfo() != null && prop.isLocal()) {
|
||||
local.add(prop);
|
||||
}
|
||||
@@ -180,7 +203,7 @@ public class DeployBeanPropertyLists {
|
||||
|
||||
public BeanProperty getId() {
|
||||
if (ids.size() > 1) {
|
||||
String msg = "Ebean does not support multiple @Id properties. You need to convert to using an @EmbeddedId."
|
||||
String msg = "Issue with bean "+desc+". Ebean does not support multiple @Id properties. You need to convert to using an @EmbeddedId."
|
||||
+" Please email the ebean google group if you need further clarification.";
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
@@ -206,6 +229,10 @@ public class DeployBeanPropertyLists {
|
||||
return (BeanProperty[]) local.toArray(new BeanProperty[local.size()]);
|
||||
}
|
||||
|
||||
public BeanProperty[] getMutable() {
|
||||
return (BeanProperty[]) mutable.toArray(new BeanProperty[mutable.size()]);
|
||||
}
|
||||
|
||||
public BeanPropertyAssocOne<?>[] getEmbedded() {
|
||||
return (BeanPropertyAssocOne[]) embedded.toArray(new BeanPropertyAssocOne[embedded.size()]);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.avaje.ebeaninternal.server.deploy.BeanCascadeInfo;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanTable;
|
||||
import com.avaje.ebeaninternal.server.deploy.InheritInfo;
|
||||
import com.avaje.ebeaninternal.server.deploy.TableJoin;
|
||||
import com.avaje.ebeaninternal.server.query.SqlJoinType;
|
||||
|
||||
/**
|
||||
* Represents a join to another table during deployment phase.
|
||||
@@ -32,7 +33,7 @@ public class DeployTableJoin {
|
||||
/**
|
||||
* The type of join. LEFT OUTER etc.
|
||||
*/
|
||||
private String type = TableJoin.JOIN;
|
||||
private SqlJoinType type = SqlJoinType.INNER;
|
||||
|
||||
/**
|
||||
* The list of properties mapped to this joined table.
|
||||
@@ -162,7 +163,7 @@ public class DeployTableJoin {
|
||||
/**
|
||||
* Return the type of join. LEFT OUTER JOIN etc.
|
||||
*/
|
||||
public String getType() {
|
||||
public SqlJoinType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -170,7 +171,11 @@ public class DeployTableJoin {
|
||||
* Return true if this join is a left outer join.
|
||||
*/
|
||||
public boolean isOuterJoin() {
|
||||
return type.equals(TableJoin.LEFT_OUTER);
|
||||
return type == SqlJoinType.OUTER;
|
||||
}
|
||||
|
||||
private void setType(SqlJoinType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,13 +184,13 @@ public class DeployTableJoin {
|
||||
public void setType(String joinType) {
|
||||
joinType = joinType.toUpperCase();
|
||||
if (joinType.equalsIgnoreCase(TableJoin.JOIN)) {
|
||||
type = TableJoin.JOIN;
|
||||
type = SqlJoinType.INNER;
|
||||
} else if (joinType.indexOf("LEFT") > -1) {
|
||||
type = TableJoin.LEFT_OUTER;
|
||||
type = SqlJoinType.OUTER;
|
||||
} else if (joinType.indexOf("OUTER") > -1) {
|
||||
type = TableJoin.LEFT_OUTER;
|
||||
type = SqlJoinType.OUTER;
|
||||
} else if (joinType.indexOf("INNER") > -1) {
|
||||
type = TableJoin.JOIN;
|
||||
type = SqlJoinType.INNER;
|
||||
} else {
|
||||
throw new RuntimeException(Message.msg("join.type.unknown", joinType));
|
||||
}
|
||||
|
||||
@@ -107,10 +107,13 @@ public class AnnotationAssocOnes extends AnnotationParser {
|
||||
BeanTable beanTable = prop.getBeanTable();
|
||||
JoinColumn joinColumn = get(prop, JoinColumn.class);
|
||||
if (joinColumn != null) {
|
||||
prop.getTableJoin().addJoinColumn(false, joinColumn, beanTable);
|
||||
if (!joinColumn.updatable()){
|
||||
prop.setDbUpdateable(false);
|
||||
}
|
||||
prop.getTableJoin().addJoinColumn(false, joinColumn, beanTable);
|
||||
if (!joinColumn.updatable()) {
|
||||
prop.setDbUpdateable(false);
|
||||
}
|
||||
if (!joinColumn.nullable()) {
|
||||
prop.setNullable(false);
|
||||
}
|
||||
}
|
||||
|
||||
JoinColumns joinColumns = get(prop, JoinColumns.class);
|
||||
|
||||
+366
-341
@@ -10,6 +10,7 @@ import java.util.Iterator;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
import com.avaje.ebean.annotation.ColumnHstore;
|
||||
import com.avaje.ebeaninternal.server.core.Message;
|
||||
import com.avaje.ebeaninternal.server.deploy.DetermineManyType;
|
||||
import com.avaje.ebeaninternal.server.deploy.ManyType;
|
||||
@@ -21,8 +22,10 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyCompound;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertySimpleCollection;
|
||||
import com.avaje.ebeaninternal.server.type.CtCompoundType;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarType;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarTypePostgresHstore;
|
||||
import com.avaje.ebeaninternal.server.type.TypeManager;
|
||||
import com.avaje.ebeaninternal.server.type.reflect.CheckImmutableResponse;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -40,355 +43,377 @@ public class DeployCreateProperties {
|
||||
private final DetermineManyType determineManyType;
|
||||
|
||||
private final TypeManager typeManager;
|
||||
|
||||
public DeployCreateProperties(TypeManager typeManager) {
|
||||
this.typeManager = typeManager;
|
||||
this.determineManyType = new DetermineManyType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the appropriate properties for a bean.
|
||||
*/
|
||||
public void createProperties(DeployBeanDescriptor<?> desc) {
|
||||
|
||||
createProperties(desc, desc.getBeanType(), 0);
|
||||
desc.sortProperties();
|
||||
|
||||
// check the transient properties...
|
||||
Iterator<DeployBeanProperty> it = desc.propertiesAll();
|
||||
|
||||
while (it.hasNext()) {
|
||||
DeployBeanProperty prop = it.next();
|
||||
if (prop.isTransient()){
|
||||
if (prop.getWriteMethod() == null || prop.getReadMethod() == null){
|
||||
// Typically a helper method ... this is expected
|
||||
logger.trace("... transient: "+prop.getFullBeanName());
|
||||
} else {
|
||||
// dubious, possible error...
|
||||
String msg = Message.msg("deploy.property.nofield", desc.getFullName(), prop.getName());
|
||||
logger.warn(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we should ignore this field.
|
||||
* <p>
|
||||
* We want to ignore ebean internal fields and some others as well.
|
||||
* </p>
|
||||
*/
|
||||
private boolean ignoreFieldByName(String fieldName) {
|
||||
if (fieldName.startsWith("_ebean_")){
|
||||
// ignore Ebean internal fields
|
||||
return true;
|
||||
}
|
||||
if (fieldName.startsWith("ajc$instance$")) {
|
||||
// ignore AspectJ internal fields
|
||||
return true;
|
||||
}
|
||||
public DeployCreateProperties(TypeManager typeManager) {
|
||||
this.typeManager = typeManager;
|
||||
this.determineManyType = new DetermineManyType();
|
||||
}
|
||||
|
||||
// we are interested in this field
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* reflect the bean properties from Class. Some of these properties may not
|
||||
* map to database columns.
|
||||
*/
|
||||
private void createProperties(DeployBeanDescriptor<?> desc, Class<?> beanType, int level) {
|
||||
/**
|
||||
* Create the appropriate properties for a bean.
|
||||
*/
|
||||
public void createProperties(DeployBeanDescriptor<?> desc) {
|
||||
|
||||
boolean scalaObject = desc.isScalaObject();
|
||||
createProperties(desc, desc.getBeanType(), 0);
|
||||
desc.sortProperties();
|
||||
|
||||
try {
|
||||
Method[] declaredMethods = beanType.getDeclaredMethods();
|
||||
Field[] fields = beanType.getDeclaredFields();
|
||||
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
|
||||
Field field = fields[i];
|
||||
if (Modifier.isStatic(field.getModifiers())) {
|
||||
// not interested in static fields
|
||||
|
||||
} else if (Modifier.isTransient(field.getModifiers())) {
|
||||
// not interested in transient fields
|
||||
logger.trace("Skipping transient field "+field.getName()+" in "+beanType.getName());
|
||||
// check the transient properties...
|
||||
Iterator<DeployBeanProperty> it = desc.propertiesAll();
|
||||
|
||||
} else if (ignoreFieldByName(field.getName())) {
|
||||
// not interested this field (ebean or aspectJ field)
|
||||
|
||||
} else {
|
||||
|
||||
String fieldName = getFieldName(field, beanType);
|
||||
String initFieldName = initCap(fieldName);
|
||||
|
||||
Method getter = findGetter(field, initFieldName, declaredMethods, scalaObject);
|
||||
Method setter = findSetter(field, initFieldName, declaredMethods, scalaObject);
|
||||
|
||||
DeployBeanProperty prop = createProp(level, desc, field, beanType, getter, setter);
|
||||
if (prop == null){
|
||||
// transient annotation on unsupported type
|
||||
|
||||
} else {
|
||||
// set a order that gives priority to inherited properties
|
||||
// push Id/EmbeddedId up and CreatedTimestamp/UpdatedTimestamp down
|
||||
int sortOverride = prop.getSortOverride();
|
||||
prop.setSortOrder((level*10000+100-i + sortOverride));
|
||||
|
||||
DeployBeanProperty replaced = desc.addBeanProperty(prop);
|
||||
if (replaced != null){
|
||||
if (replaced.isTransient()) {
|
||||
// expected for inheritance...
|
||||
} else {
|
||||
String msg = "Huh??? property "+prop.getFullBeanName()+" being defined twice";
|
||||
msg += " but replaced property was not transient? This is not expected?";
|
||||
logger.warn(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> superClass = beanType.getSuperclass();
|
||||
|
||||
if (!superClass.equals(Object.class)) {
|
||||
// recursively add any properties in the inheritance heirarchy
|
||||
// up to the Object.class level...
|
||||
createProperties(desc, superClass, level + 1);
|
||||
}
|
||||
|
||||
} catch (PersistenceException ex) {
|
||||
throw ex;
|
||||
|
||||
} catch (Exception ex) {
|
||||
throw new PersistenceException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the first letter of the string upper case.
|
||||
*/
|
||||
private String initCap(String str){
|
||||
if (str.length() > 1){
|
||||
return Character.toUpperCase(str.charAt(0))+str.substring(1);
|
||||
} else {
|
||||
// only a single char
|
||||
return str.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean spec field name (trim of "is" from boolean types)
|
||||
*/
|
||||
private String getFieldName(Field field, Class<?> beanType){
|
||||
|
||||
String name = field.getName();
|
||||
|
||||
if ((Boolean.class.equals(field.getType()) || boolean.class.equals(field.getType()))
|
||||
&& name.startsWith("is") && name.length() > 2){
|
||||
|
||||
// it is a boolean type field starting with "is"
|
||||
char c = name.charAt(2);
|
||||
if (Character.isUpperCase(c)){
|
||||
String msg = "trimming off 'is' from boolean field name "+name+" in class "+beanType.getName();
|
||||
logger.info(msg);
|
||||
|
||||
return name.substring(2);
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a public non-static getter method that matches this field (according to bean-spec rules).
|
||||
*/
|
||||
private Method findGetter(Field field, String initFieldName, Method[] declaredMethods, boolean scalaObject){
|
||||
|
||||
String methGetName = "get"+initFieldName;
|
||||
String methIsName = "is"+initFieldName;
|
||||
String scalaGet = field.getName();
|
||||
|
||||
for (int i = 0; i < declaredMethods.length; i++) {
|
||||
Method m = declaredMethods[i];
|
||||
if ((scalaObject && m.getName().equals(scalaGet))
|
||||
|| m.getName().equals(methGetName) || m.getName().equals(methIsName)){
|
||||
|
||||
Class<?>[] params = m.getParameterTypes();
|
||||
if (params.length == 0){
|
||||
if (field.getType().equals(m.getReturnType())){
|
||||
int modifiers = m.getModifiers();
|
||||
if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) {
|
||||
// we find it...
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a public non-static setter method that matches this field (according to bean-spec rules).
|
||||
*/
|
||||
private Method findSetter(Field field, String initFieldName, Method[] declaredMethods, boolean scalaObject){
|
||||
|
||||
String methSetName = "set"+initFieldName;
|
||||
String scalaSetName = field.getName()+"_$eq";
|
||||
|
||||
for (int i = 0; i < declaredMethods.length; i++) {
|
||||
Method m = declaredMethods[i];
|
||||
|
||||
if ((scalaObject && m.getName().equals(scalaSetName))
|
||||
|| m.getName().equals(methSetName)){
|
||||
|
||||
Class<?>[] params = m.getParameterTypes();
|
||||
if (params.length == 1 && field.getType().equals(params[0])){
|
||||
if (void.class.equals(m.getReturnType())){
|
||||
int modifiers = m.getModifiers();
|
||||
if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private DeployBeanProperty createManyType(DeployBeanDescriptor<?> desc, Class<?> targetType, ManyType manyType) {
|
||||
|
||||
try {
|
||||
ScalarType<?> scalarType = typeManager.getScalarType(targetType);
|
||||
if (scalarType != null) {
|
||||
return new DeployBeanPropertySimpleCollection(desc, targetType, scalarType, manyType);
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
logger.debug("expected non-scalar type"+e.getMessage());
|
||||
}
|
||||
//TODO: Handle Collection of CompoundType and Embedded Type
|
||||
return new DeployBeanPropertyAssocMany(desc, targetType, manyType);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private DeployBeanProperty createProp(DeployBeanDescriptor<?> desc, Field field) {
|
||||
|
||||
Class<?> propertyType = field.getType();
|
||||
Class<?> innerType = propertyType;
|
||||
|
||||
// check for Collection type (list, set or map)
|
||||
ManyType manyType = determineManyType.getManyType(propertyType);
|
||||
|
||||
if (manyType != null) {
|
||||
// List, Set or Map based object
|
||||
Class<?> targetType = determineTargetType(field);
|
||||
if (targetType == null){
|
||||
Transient transAnnotation = field.getAnnotation(Transient.class);
|
||||
if (transAnnotation != null) {
|
||||
// not supporting this field (generic type used)
|
||||
return null;
|
||||
}
|
||||
logger.warn("Could not find parameter type (via reflection) on "+desc.getFullName()+" "+field.getName());
|
||||
}
|
||||
return createManyType(desc, targetType, manyType);
|
||||
}
|
||||
|
||||
if (innerType.isEnum() || innerType.isPrimitive()){
|
||||
return new DeployBeanProperty(desc, propertyType, null, null);
|
||||
}
|
||||
|
||||
ScalarType<?> scalarType = typeManager.getScalarType(innerType);
|
||||
if (scalarType != null) {
|
||||
return new DeployBeanProperty(desc, propertyType, scalarType, null);
|
||||
}
|
||||
|
||||
CtCompoundType<?> compoundType = typeManager.getCompoundType(innerType);
|
||||
if (compoundType != null) {
|
||||
return new DeployBeanPropertyCompound(desc, propertyType, compoundType, null);
|
||||
}
|
||||
|
||||
if (!isTransientField(field)){
|
||||
try {
|
||||
CheckImmutableResponse checkImmutable = typeManager.checkImmutable(innerType);
|
||||
if (checkImmutable.isImmutable()){
|
||||
if (checkImmutable.isCompoundType()){
|
||||
// use reflection to support compound immutable value objects
|
||||
typeManager.recursiveCreateScalarDataReader(innerType);
|
||||
compoundType = typeManager.getCompoundType(innerType);
|
||||
if (compoundType != null) {
|
||||
return new DeployBeanPropertyCompound(desc, propertyType, compoundType, null);
|
||||
}
|
||||
|
||||
} else {
|
||||
// use reflection to support simple immutable value objects
|
||||
scalarType = typeManager.recursiveCreateScalarTypes(innerType);
|
||||
return new DeployBeanProperty(desc, propertyType, scalarType, null);
|
||||
}
|
||||
}
|
||||
} catch (Exception e){
|
||||
logger.error("Error with " + desc + " field:" + field.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return new DeployBeanPropertyAssocOne(desc, propertyType);
|
||||
}
|
||||
|
||||
private boolean isTransientField(Field field) {
|
||||
|
||||
Transient t = field.getAnnotation(Transient.class);
|
||||
return (t != null);
|
||||
}
|
||||
|
||||
private DeployBeanProperty createProp(int level, DeployBeanDescriptor<?> desc, Field field, Class<?> beanType, Method getter, Method setter) {
|
||||
|
||||
DeployBeanProperty prop = createProp(desc, field);
|
||||
if (prop == null){
|
||||
// transient annotation on unsupported type
|
||||
return null;
|
||||
while (it.hasNext()) {
|
||||
DeployBeanProperty prop = it.next();
|
||||
if (prop.isTransient()) {
|
||||
if (prop.getWriteMethod() == null || prop.getReadMethod() == null) {
|
||||
// Typically a helper method ... this is expected
|
||||
logger.trace("... transient: " + prop.getFullBeanName());
|
||||
} else {
|
||||
prop.setOwningType(beanType);
|
||||
prop.setName(field.getName());
|
||||
|
||||
// the getter or setter could be null if we are using
|
||||
// javaagent type enhancement. If we are using subclass
|
||||
// generation then we do need to find the getter and setter
|
||||
prop.setReadMethod(getter);
|
||||
prop.setWriteMethod(setter);
|
||||
prop.setField(field);
|
||||
return prop;
|
||||
// dubious, possible error...
|
||||
String msg = Message.msg("deploy.property.nofield", desc.getFullName(), prop.getName());
|
||||
logger.warn(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we should ignore this field.
|
||||
* <p>
|
||||
* We want to ignore ebean internal fields and some others as well.
|
||||
* </p>
|
||||
*/
|
||||
private boolean ignoreFieldByName(String fieldName) {
|
||||
if (fieldName.startsWith("_ebean_")) {
|
||||
// ignore Ebean internal fields
|
||||
return true;
|
||||
}
|
||||
if (fieldName.startsWith("ajc$instance$")) {
|
||||
// ignore AspectJ internal fields
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the type of the List,Set or Map. Not been set explicitly so
|
||||
* determine this from ParameterizedType.
|
||||
*/
|
||||
private Class<?> determineTargetType(Field field) {
|
||||
|
||||
Type genType = field.getGenericType();
|
||||
if (genType instanceof ParameterizedType) {
|
||||
ParameterizedType ptype = (ParameterizedType) genType;
|
||||
// we are interested in this field
|
||||
return false;
|
||||
}
|
||||
|
||||
Type[] typeArgs = ptype.getActualTypeArguments();
|
||||
if (typeArgs.length == 1) {
|
||||
// probably a Set or List
|
||||
if (typeArgs[0] instanceof Class<?>){
|
||||
return (Class<?>) typeArgs[0];
|
||||
}
|
||||
//throw new RuntimeException("Unexpected Parameterised Type? "+typeArgs[0]);
|
||||
return null;
|
||||
}
|
||||
if (typeArgs.length == 2) {
|
||||
// this is probably a Map
|
||||
if (typeArgs[1] instanceof ParameterizedType) {
|
||||
// not supporting ParameterizedType on Map.
|
||||
return null;
|
||||
}
|
||||
return (Class<?>) typeArgs[1];
|
||||
}
|
||||
}
|
||||
// if targetType is null, then must be set in annotations
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* reflect the bean properties from Class. Some of these properties may not map to database
|
||||
* columns.
|
||||
*/
|
||||
private void createProperties(DeployBeanDescriptor<?> desc, Class<?> beanType, int level) {
|
||||
|
||||
boolean scalaObject = desc.isScalaObject();
|
||||
|
||||
try {
|
||||
Method[] declaredMethods = beanType.getDeclaredMethods();
|
||||
Field[] fields = beanType.getDeclaredFields();
|
||||
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
|
||||
Field field = fields[i];
|
||||
if (Modifier.isStatic(field.getModifiers())) {
|
||||
// not interested in static fields
|
||||
|
||||
} else if (Modifier.isTransient(field.getModifiers())) {
|
||||
// not interested in transient fields
|
||||
logger.trace("Skipping transient field " + field.getName() + " in " + beanType.getName());
|
||||
|
||||
} else if (ignoreFieldByName(field.getName())) {
|
||||
// not interested this field (ebean or aspectJ field)
|
||||
|
||||
} else {
|
||||
|
||||
String fieldName = getFieldName(field, beanType);
|
||||
String initFieldName = initCap(fieldName);
|
||||
|
||||
Method getter = findGetter(field, initFieldName, declaredMethods, scalaObject);
|
||||
Method setter = findSetter(field, initFieldName, declaredMethods, scalaObject);
|
||||
|
||||
DeployBeanProperty prop = createProp(level, desc, field, beanType, getter, setter);
|
||||
if (prop == null) {
|
||||
// transient annotation on unsupported type
|
||||
|
||||
} else {
|
||||
// set a order that gives priority to inherited properties
|
||||
// push Id/EmbeddedId up and CreatedTimestamp/UpdatedTimestamp down
|
||||
int sortOverride = prop.getSortOverride();
|
||||
prop.setSortOrder((level * 10000 + 100 - i + sortOverride));
|
||||
|
||||
DeployBeanProperty replaced = desc.addBeanProperty(prop);
|
||||
if (replaced != null) {
|
||||
if (replaced.isTransient()) {
|
||||
// expected for inheritance...
|
||||
} else {
|
||||
String msg = "Huh??? property " + prop.getFullBeanName() + " being defined twice";
|
||||
msg += " but replaced property was not transient? This is not expected?";
|
||||
logger.warn(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> superClass = beanType.getSuperclass();
|
||||
|
||||
if (!superClass.equals(Object.class)) {
|
||||
// recursively add any properties in the inheritance heirarchy
|
||||
// up to the Object.class level...
|
||||
createProperties(desc, superClass, level + 1);
|
||||
}
|
||||
|
||||
} catch (PersistenceException ex) {
|
||||
throw ex;
|
||||
|
||||
} catch (Exception ex) {
|
||||
throw new PersistenceException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the first letter of the string upper case.
|
||||
*/
|
||||
private String initCap(String str) {
|
||||
if (str.length() > 1) {
|
||||
return Character.toUpperCase(str.charAt(0)) + str.substring(1);
|
||||
} else {
|
||||
// only a single char
|
||||
return str.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean spec field name (trim of "is" from boolean types)
|
||||
*/
|
||||
private String getFieldName(Field field, Class<?> beanType) {
|
||||
|
||||
String name = field.getName();
|
||||
|
||||
if ((Boolean.class.equals(field.getType()) || boolean.class.equals(field.getType())) && name.startsWith("is")
|
||||
&& name.length() > 2) {
|
||||
|
||||
// it is a boolean type field starting with "is"
|
||||
char c = name.charAt(2);
|
||||
if (Character.isUpperCase(c)) {
|
||||
String msg = "trimming off 'is' from boolean field name " + name + " in class " + beanType.getName();
|
||||
logger.info(msg);
|
||||
|
||||
return name.substring(2);
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a public non-static getter method that matches this field (according to bean-spec rules).
|
||||
*/
|
||||
private Method findGetter(Field field, String initFieldName, Method[] declaredMethods, boolean scalaObject) {
|
||||
|
||||
String methGetName = "get" + initFieldName;
|
||||
String methIsName = "is" + initFieldName;
|
||||
String scalaGet = field.getName();
|
||||
|
||||
for (int i = 0; i < declaredMethods.length; i++) {
|
||||
Method m = declaredMethods[i];
|
||||
if ((scalaObject && m.getName().equals(scalaGet)) || m.getName().equals(methGetName)
|
||||
|| m.getName().equals(methIsName)) {
|
||||
|
||||
Class<?>[] params = m.getParameterTypes();
|
||||
if (params.length == 0) {
|
||||
if (field.getType().equals(m.getReturnType())) {
|
||||
int modifiers = m.getModifiers();
|
||||
if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) {
|
||||
// we find it...
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a public non-static setter method that matches this field (according to bean-spec rules).
|
||||
*/
|
||||
private Method findSetter(Field field, String initFieldName, Method[] declaredMethods, boolean scalaObject) {
|
||||
|
||||
String methSetName = "set" + initFieldName;
|
||||
String scalaSetName = field.getName() + "_$eq";
|
||||
|
||||
for (int i = 0; i < declaredMethods.length; i++) {
|
||||
Method m = declaredMethods[i];
|
||||
|
||||
if ((scalaObject && m.getName().equals(scalaSetName)) || m.getName().equals(methSetName)) {
|
||||
|
||||
Class<?>[] params = m.getParameterTypes();
|
||||
if (params.length == 1 && field.getType().equals(params[0])) {
|
||||
if (void.class.equals(m.getReturnType())) {
|
||||
int modifiers = m.getModifiers();
|
||||
if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private DeployBeanProperty createManyType(DeployBeanDescriptor<?> desc, Class<?> targetType, ManyType manyType) {
|
||||
|
||||
try {
|
||||
ScalarType<?> scalarType = typeManager.getScalarType(targetType);
|
||||
if (scalarType != null) {
|
||||
return new DeployBeanPropertySimpleCollection(desc, targetType, scalarType, manyType);
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
logger.debug("expected non-scalar type" + e.getMessage());
|
||||
}
|
||||
// TODO: Handle Collection of CompoundType and Embedded Type
|
||||
return new DeployBeanPropertyAssocMany(desc, targetType, manyType);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private DeployBeanProperty createProp(DeployBeanDescriptor<?> desc, Field field) {
|
||||
|
||||
Class<?> propertyType = field.getType();
|
||||
Class<?> innerType = propertyType;
|
||||
|
||||
String specialTypeKey = getSpecialScalarType(field);
|
||||
if (specialTypeKey != null) {
|
||||
ScalarType<?> scalarType = typeManager.getScalarTypeFromKey(specialTypeKey);
|
||||
if (scalarType == null) {
|
||||
logger.error("Could not find ScalarType to match key ["+specialTypeKey+"]");
|
||||
} else {
|
||||
return new DeployBeanProperty(desc, propertyType, scalarType, null);
|
||||
}
|
||||
}
|
||||
|
||||
// check for Collection type (list, set or map)
|
||||
ManyType manyType = determineManyType.getManyType(propertyType);
|
||||
|
||||
if (manyType != null) {
|
||||
// List, Set or Map based object
|
||||
Class<?> targetType = determineTargetType(field);
|
||||
if (targetType == null) {
|
||||
Transient transAnnotation = field.getAnnotation(Transient.class);
|
||||
if (transAnnotation != null) {
|
||||
// not supporting this field (generic type used)
|
||||
return null;
|
||||
}
|
||||
logger.warn("Could not find parameter type (via reflection) on " + desc.getFullName() + " " + field.getName());
|
||||
}
|
||||
return createManyType(desc, targetType, manyType);
|
||||
}
|
||||
|
||||
if (innerType.isEnum() || innerType.isPrimitive()) {
|
||||
return new DeployBeanProperty(desc, propertyType, null, null);
|
||||
}
|
||||
|
||||
ScalarType<?> scalarType = typeManager.getScalarType(innerType);
|
||||
if (scalarType != null) {
|
||||
return new DeployBeanProperty(desc, propertyType, scalarType, null);
|
||||
}
|
||||
|
||||
CtCompoundType<?> compoundType = typeManager.getCompoundType(innerType);
|
||||
if (compoundType != null) {
|
||||
return new DeployBeanPropertyCompound(desc, propertyType, compoundType, null);
|
||||
}
|
||||
|
||||
if (isTransientField(field)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
CheckImmutableResponse checkImmutable = typeManager.checkImmutable(innerType);
|
||||
if (checkImmutable.isImmutable()) {
|
||||
if (checkImmutable.isCompoundType()) {
|
||||
// use reflection to support compound immutable value objects
|
||||
typeManager.recursiveCreateScalarDataReader(innerType);
|
||||
compoundType = typeManager.getCompoundType(innerType);
|
||||
if (compoundType != null) {
|
||||
return new DeployBeanPropertyCompound(desc, propertyType, compoundType, null);
|
||||
}
|
||||
|
||||
} else {
|
||||
// use reflection to support simple immutable value objects
|
||||
scalarType = typeManager.recursiveCreateScalarTypes(innerType);
|
||||
return new DeployBeanProperty(desc, propertyType, scalarType, null);
|
||||
}
|
||||
}
|
||||
|
||||
return new DeployBeanPropertyAssocOne(desc, propertyType);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error with " + desc + " field:" + field.getName(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String getSpecialScalarType(Field field) {
|
||||
|
||||
if (field.getAnnotation(ColumnHstore.class) != null) {
|
||||
return ScalarTypePostgresHstore.KEY;
|
||||
};
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isTransientField(Field field) {
|
||||
|
||||
Transient t = field.getAnnotation(Transient.class);
|
||||
return (t != null);
|
||||
}
|
||||
|
||||
private DeployBeanProperty createProp(int level, DeployBeanDescriptor<?> desc, Field field, Class<?> beanType,
|
||||
Method getter, Method setter) {
|
||||
|
||||
DeployBeanProperty prop = createProp(desc, field);
|
||||
if (prop == null) {
|
||||
// transient annotation on unsupported type
|
||||
return null;
|
||||
} else {
|
||||
prop.setOwningType(beanType);
|
||||
prop.setName(field.getName());
|
||||
|
||||
// the getter or setter could be null if we are using
|
||||
// javaagent type enhancement. If we are using subclass
|
||||
// generation then we do need to find the getter and setter
|
||||
prop.setReadMethod(getter);
|
||||
prop.setWriteMethod(setter);
|
||||
prop.setField(field);
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the type of the List,Set or Map. Not been set explicitly so determine this from
|
||||
* ParameterizedType.
|
||||
*/
|
||||
private Class<?> determineTargetType(Field field) {
|
||||
|
||||
Type genType = field.getGenericType();
|
||||
if (genType instanceof ParameterizedType) {
|
||||
ParameterizedType ptype = (ParameterizedType) genType;
|
||||
|
||||
Type[] typeArgs = ptype.getActualTypeArguments();
|
||||
if (typeArgs.length == 1) {
|
||||
// probably a Set or List
|
||||
if (typeArgs[0] instanceof Class<?>) {
|
||||
return (Class<?>) typeArgs[0];
|
||||
}
|
||||
// throw new RuntimeException("Unexpected Parameterised Type? "+typeArgs[0]);
|
||||
return null;
|
||||
}
|
||||
if (typeArgs.length == 2) {
|
||||
// this is probably a Map
|
||||
if (typeArgs[1] instanceof ParameterizedType) {
|
||||
// not supporting ParameterizedType on Map.
|
||||
return null;
|
||||
}
|
||||
return (Class<?>) typeArgs[1];
|
||||
}
|
||||
}
|
||||
// if targetType is null, then must be set in annotations
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +135,8 @@ public class DeployInherit {
|
||||
}
|
||||
DiscriminatorColumn da = (DiscriminatorColumn) cls.getAnnotation(DiscriminatorColumn.class);
|
||||
if (da != null) {
|
||||
info.setDiscriminatorColumn(da.name());
|
||||
// lowercase the discriminator column for RawSql and JSON
|
||||
info.setDiscriminatorColumn(da.name().toLowerCase());
|
||||
DiscriminatorType discriminatorType = da.discriminatorType();
|
||||
if (discriminatorType.equals(DiscriminatorType.INTEGER)){
|
||||
info.setDiscriminatorType(Types.INTEGER);
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.avaje.ebeaninternal.server.expression;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import com.avaje.ebean.event.BeanQueryRequest;
|
||||
@@ -115,7 +114,7 @@ class AllEqualsExpression implements SpiExpression {
|
||||
|
||||
int hc = 31;
|
||||
for (Object value : propMap.values()) {
|
||||
hc = hc * 31 + Objects.hashCode(value);
|
||||
hc = hc * 31 + (value == null ? 0 : value.hashCode());
|
||||
}
|
||||
|
||||
return hc;
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.avaje.ebean.FutureList;
|
||||
import com.avaje.ebean.FutureRowCount;
|
||||
import com.avaje.ebean.Junction;
|
||||
import com.avaje.ebean.OrderBy;
|
||||
import com.avaje.ebean.PagedList;
|
||||
import com.avaje.ebean.PagingList;
|
||||
import com.avaje.ebean.QueryIterator;
|
||||
import com.avaje.ebean.QueryResultVisitor;
|
||||
@@ -39,7 +40,7 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
|
||||
private static final long serialVersionUID = -645619859900030678L;
|
||||
|
||||
Conjunction(com.avaje.ebean.Query<T> query, ExpressionList<T> parent) {
|
||||
super(AND, query, parent);
|
||||
super(false, AND, query, parent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,17 +49,21 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
|
||||
private static final long serialVersionUID = -8464470066692221413L;
|
||||
|
||||
Disjunction(com.avaje.ebean.Query<T> query, ExpressionList<T> parent) {
|
||||
super(OR, query, parent);
|
||||
super(true, OR, query, parent);
|
||||
}
|
||||
}
|
||||
|
||||
// private final ArrayList<SpiExpression> list = new
|
||||
// ArrayList<SpiExpression>();
|
||||
private final DefaultExpressionList<T> exprList;
|
||||
|
||||
private final String joinType;
|
||||
|
||||
JunctionExpression(String joinType, com.avaje.ebean.Query<T> query, ExpressionList<T> parent) {
|
||||
/**
|
||||
* If true then a disjunction which means outer joins are required.
|
||||
*/
|
||||
private final boolean disjunction;
|
||||
|
||||
JunctionExpression(boolean disjunction, String joinType, com.avaje.ebean.Query<T> query, ExpressionList<T> parent) {
|
||||
this.disjunction = disjunction;
|
||||
this.joinType = joinType;
|
||||
this.exprList = new DefaultExpressionList<T>(query, parent);
|
||||
}
|
||||
@@ -67,9 +72,20 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
|
||||
|
||||
List<SpiExpression> list = exprList.internalList();
|
||||
|
||||
// get the current state for 'require outer joins'
|
||||
boolean parentOuterJoins = manyWhereJoin.isRequireOuterJoins();
|
||||
if (disjunction) {
|
||||
// turn on outer joins required for disjunction expressions
|
||||
manyWhereJoin.setRequireOuterJoins(true);
|
||||
}
|
||||
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
list.get(i).containsMany(desc, manyWhereJoin);
|
||||
}
|
||||
if (disjunction && !parentOuterJoins) {
|
||||
// restore state to not forcing outer joins
|
||||
manyWhereJoin.setRequireOuterJoins(false);
|
||||
}
|
||||
}
|
||||
|
||||
public Junction<T> add(Expression item) {
|
||||
@@ -229,6 +245,11 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
|
||||
public PagingList<T> findPagingList(int pageSize) {
|
||||
return exprList.findPagingList(pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PagedList<T> findPagedList(int pageIndex, int pageSize) {
|
||||
return exprList.findPagedList(pageIndex, pageSize);
|
||||
}
|
||||
|
||||
public int findRowCount() {
|
||||
return exprList.findRowCount();
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.avaje.ebeaninternal.server.lib.util;
|
||||
|
||||
/**
|
||||
* String utility for adding strings together.
|
||||
* <p>
|
||||
* Predicts a decent buffer size to append the strings into.
|
||||
*/
|
||||
public class Str {
|
||||
|
||||
/**
|
||||
* Append strings together.
|
||||
*/
|
||||
public static String add(String s0, String s1, String ... args) {
|
||||
|
||||
// determine a decent buffer size
|
||||
int len = 16 + s0.length() + s1.length();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
len += args[i].length();
|
||||
}
|
||||
|
||||
// append all the strings into the buffer
|
||||
StringBuilder sb = new StringBuilder(len);
|
||||
sb.append(s0).append(s1);
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
sb.append(args[i]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Append two strings together.
|
||||
*/
|
||||
public static String add(String s0, String s1) {
|
||||
StringBuilder sb = new StringBuilder(s0.length() + s1.length() + 5);
|
||||
return sb.append(s0).append(s1).toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -56,6 +56,13 @@ public abstract class DLoadBaseContext {
|
||||
if (queryProps == null) {
|
||||
return batchSize;
|
||||
}
|
||||
|
||||
int queryFetchBatch = queryProps.getQueryFetchBatch();
|
||||
if (queryFetchBatch > 0) {
|
||||
// property join was automatically set to a 'query join'
|
||||
return queryFetchBatch;
|
||||
}
|
||||
|
||||
FetchConfig fetchConfig = queryProps.getFetchConfig();
|
||||
if (fetchConfig == null) {
|
||||
return batchSize;
|
||||
|
||||
@@ -29,8 +29,9 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
|
||||
|
||||
super(parent, desc, path, defaultBatchSize, queryProps);
|
||||
|
||||
// bufferList only required when using query joins (queryFetch)
|
||||
this.bufferList = (!queryFetch) ? null : new ArrayList<DLoadBeanContext.LoadBuffer>();
|
||||
this.currentBuffer = createBuffer(firstBatchSize);
|
||||
this.bufferList = queryFetch ? new ArrayList<DLoadBeanContext.LoadBuffer>() : null;
|
||||
}
|
||||
|
||||
protected void configureQuery(SpiQuery<?> query, String lazyLoadProperty) {
|
||||
@@ -52,11 +53,12 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
|
||||
|
||||
protected void register(EntityBeanIntercept ebi){
|
||||
|
||||
ebi.setBeanLoader(0, currentBuffer, getPersistenceContext());
|
||||
if (currentBuffer.add(ebi)) {
|
||||
// the currentBuffer is full so create another one
|
||||
currentBuffer = createBuffer(secondaryBatchSize);
|
||||
if (currentBuffer.isFull()) {
|
||||
currentBuffer = createBuffer(secondaryBatchSize);
|
||||
}
|
||||
// set the persistenceContext on the bean first
|
||||
ebi.setBeanLoader(0, currentBuffer, getPersistenceContext());
|
||||
currentBuffer.add(ebi);
|
||||
}
|
||||
|
||||
private LoadBuffer createBuffer(int size) {
|
||||
@@ -78,7 +80,7 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
|
||||
for (LoadBuffer loadBuffer : bufferList) {
|
||||
if (!loadBuffer.list.isEmpty()) {
|
||||
boolean loadCache = false;
|
||||
LoadBeanRequest req = new LoadBeanRequest(loadBuffer, parentRequest.getTransaction(), false, null, loadCache);
|
||||
LoadBeanRequest req = new LoadBeanRequest(loadBuffer, parentRequest, false, null, loadCache);
|
||||
|
||||
parent.getEbeanServer().loadBean(req);
|
||||
if (!queryProps.isQueryFetchAll()) {
|
||||
@@ -102,20 +104,31 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
|
||||
private final DLoadBeanContext context;
|
||||
private final int batchSize;
|
||||
private final List<EntityBeanIntercept> list;
|
||||
private PersistenceContext persistenceContext;
|
||||
|
||||
public LoadBuffer(DLoadBeanContext context, int batchSize) {
|
||||
this.context = context;
|
||||
this.batchSize = batchSize;
|
||||
this.list = new ArrayList<EntityBeanIntercept>(batchSize);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if the buffer is full.
|
||||
*/
|
||||
public boolean add(EntityBeanIntercept ebi) {
|
||||
list.add(ebi);
|
||||
public boolean isFull() {
|
||||
return batchSize == list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the buffer is full.
|
||||
*/
|
||||
public void add(EntityBeanIntercept ebi) {
|
||||
if (persistenceContext == null) {
|
||||
// get persistenceContext from first loaded bean into the buffer
|
||||
persistenceContext = ebi.getPersistenceContext();
|
||||
}
|
||||
list.add(ebi);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EntityBeanIntercept> getBatch() {
|
||||
@@ -139,7 +152,7 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
|
||||
|
||||
@Override
|
||||
public PersistenceContext getPersistenceContext() {
|
||||
return context.getPersistenceContext();
|
||||
return persistenceContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -173,7 +186,7 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
|
||||
}
|
||||
}
|
||||
|
||||
LoadBeanRequest req = new LoadBeanRequest(this, null, true, ebi.getLazyLoadProperty(), context.hitCache);
|
||||
LoadBeanRequest req = new LoadBeanRequest(this, true, ebi.getLazyLoadProperty(), context.hitCache);
|
||||
context.desc.getEbeanServer().loadBean(req);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,15 +49,18 @@ public class DLoadContext implements LoadContext {
|
||||
private final Map<String,ObjectGraphNode> nodePathMap = new HashMap<String, ObjectGraphNode>();
|
||||
|
||||
private PersistenceContext persistenceContext;
|
||||
|
||||
private List<OrmQueryProperties> secQuery;
|
||||
|
||||
public DLoadContext(OrmQueryRequest<?> request) {
|
||||
|
||||
public DLoadContext(SpiEbeanServer ebeanServer, BeanDescriptor<?> rootDescriptor, Boolean readOnly, SpiQuery<?> query) {
|
||||
|
||||
this.ebeanServer = ebeanServer;
|
||||
this.defaultBatchSize = ebeanServer.getLazyLoadBatchSize();
|
||||
this.rootDescriptor = rootDescriptor;
|
||||
this.readOnly = readOnly;
|
||||
this.persistenceContext = request.getPersistenceContext();
|
||||
this.ebeanServer = request.getServer();
|
||||
this.defaultBatchSize = ebeanServer.getLazyLoadBatchSize();
|
||||
this.rootDescriptor = request.getBeanDescriptor();
|
||||
|
||||
SpiQuery<?> query = request.getQuery();
|
||||
this.readOnly = query.isReadOnly();
|
||||
this.excludeBeanCache = Boolean.FALSE.equals(query.isUseBeanCache());
|
||||
this.useAutofetchManager = query.getAutoFetchManager() != null;
|
||||
|
||||
@@ -72,7 +75,7 @@ public class DLoadContext implements LoadContext {
|
||||
|
||||
// initialise rootBeanContext after origin and relativePath have been set
|
||||
this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isExcludeBeanCache() {
|
||||
return excludeBeanCache;
|
||||
|
||||
@@ -31,14 +31,16 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
|
||||
super(parent, property.getBeanDescriptor(), path, defaultBatchSize, queryProps);
|
||||
|
||||
this.property = property;
|
||||
this.bufferList = new ArrayList<DLoadManyContext.LoadBuffer>();
|
||||
// bufferList only required when using query joins (queryFetch)
|
||||
this.bufferList = (!queryFetch) ? null : new ArrayList<DLoadManyContext.LoadBuffer>();
|
||||
this.currentBuffer = createBuffer(firstBatchSize);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private LoadBuffer createBuffer(int size) {
|
||||
LoadBuffer buffer = new LoadBuffer(this, size);
|
||||
bufferList.add(buffer);
|
||||
if (bufferList != null) {
|
||||
bufferList.add(buffer);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@@ -74,11 +76,11 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
|
||||
|
||||
public void register(BeanCollection<?> bc){
|
||||
|
||||
bc.setLoader(0, currentBuffer);
|
||||
if (currentBuffer.add(bc)) {
|
||||
// the currentBuffer is full so create another one
|
||||
if (currentBuffer.isFull()) {
|
||||
currentBuffer = createBuffer(secondaryBatchSize);
|
||||
}
|
||||
}
|
||||
currentBuffer.add(bc);
|
||||
bc.setLoader(0, currentBuffer);
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +93,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
|
||||
if (bufferList != null) {
|
||||
for (LoadBuffer loadBuffer : bufferList) {
|
||||
if (!loadBuffer.list.isEmpty()) {
|
||||
LoadManyRequest req = new LoadManyRequest(loadBuffer, parentRequest.getTransaction(), requestedBatchSize, false, false, false);
|
||||
LoadManyRequest req = new LoadManyRequest(loadBuffer, parentRequest, requestedBatchSize, false, false, false);
|
||||
parent.getEbeanServer().loadMany(req);
|
||||
if (!queryProps.isQueryFetchAll()) {
|
||||
// Stop - only fetch the first batch ... the rest will be lazy loaded
|
||||
@@ -112,22 +114,32 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
|
||||
*/
|
||||
public static class LoadBuffer implements BeanCollectionLoader, LoadManyBuffer {
|
||||
|
||||
private final PersistenceContext persistenceContext;
|
||||
private final DLoadManyContext context;
|
||||
private final int batchSize;
|
||||
private final List<BeanCollection<?>> list;
|
||||
|
||||
public LoadBuffer(DLoadManyContext context, int batchSize) {
|
||||
this.context = context;
|
||||
// set the persistence context as at this moment in
|
||||
// case it changes as part of a findIterate etc
|
||||
this.persistenceContext = context.getPersistenceContext();
|
||||
this.batchSize = batchSize;
|
||||
this.list = new ArrayList<BeanCollection<?>>(batchSize);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if the buffer is full.
|
||||
*/
|
||||
public boolean add(BeanCollection<?> bc) {
|
||||
public boolean isFull() {
|
||||
return batchSize == list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the buffer is full.
|
||||
*/
|
||||
public void add(BeanCollection<?> bc) {
|
||||
list.add(bc);
|
||||
return batchSize == list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -162,7 +174,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
|
||||
|
||||
@Override
|
||||
public PersistenceContext getPersistenceContext() {
|
||||
return context.getPersistenceContext();
|
||||
return persistenceContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -187,7 +199,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
|
||||
|
||||
// Should reduce the list by checking each beanCollection in the L2 first before executing the query
|
||||
|
||||
LoadManyRequest req = new LoadManyRequest(this, null, batchSize, true, onlyIds, useCache);
|
||||
LoadManyRequest req = new LoadManyRequest(this, batchSize, true, onlyIds, useCache);
|
||||
context.parent.getEbeanServer().loadMany(req);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +64,6 @@ public final class DefaultPersistExecute implements PersistExecute {
|
||||
BeanPersistController controller = request.getBeanController();
|
||||
if (controller == null || controller.preInsert(request)) {
|
||||
persister.insert(request);
|
||||
// NOTE: the persister fires the postInsert so that this
|
||||
// occurs before ebeanIntercept.setLoaded(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,9 +77,8 @@ public final class DefaultPersistExecute implements PersistExecute {
|
||||
|
||||
BeanPersistController controller = request.getBeanController();
|
||||
if (controller == null || controller.preUpdate(request)) {
|
||||
request.postControllerPrepareUpdate();
|
||||
persister.update(request);
|
||||
// NOTE: the persister fires the postUpdate so that this
|
||||
// occurs before ebeanIntercept.setLoaded(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,9 +92,7 @@ public final class DefaultPersistExecute implements PersistExecute {
|
||||
|
||||
BeanPersistController controller = request.getBeanController();
|
||||
if (controller == null || controller.preDelete(request)) {
|
||||
|
||||
persister.delete(request);
|
||||
// NOTE: the persister fires the postDelete
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -413,14 +413,14 @@ public final class DefaultPersister implements Persister {
|
||||
if (t.isPersistCascade()) {
|
||||
// OneToOne exported side with delete cascade
|
||||
BeanPropertyAssocOne<?>[] expOnes = descriptor.propertiesOneExportedDelete();
|
||||
for (int i = 0; i < expOnes.length; i++) {
|
||||
for (int i = 0; i < expOnes.length; i++) {
|
||||
BeanDescriptor<?> targetDesc = expOnes[i].getTargetDescriptor();
|
||||
if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) {
|
||||
SqlUpdate sqlDelete = expOnes[i].deleteByParentId(id, idList);
|
||||
executeSqlUpdate(sqlDelete, t);
|
||||
} else {
|
||||
List<Object> childIds = expOnes[i].findIdsByParentId(id, idList, t);
|
||||
delete(targetDesc, null, childIds, t);
|
||||
deleteChildrenById(t, targetDesc, childIds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,6 +558,7 @@ public final class DefaultPersister implements Persister {
|
||||
// skip saving this bean
|
||||
} else {
|
||||
t.depth(+1);
|
||||
prop.setParentBeanToChild(parentBean, detailBean);
|
||||
saveRecurse(detailBean, t, parentBean);
|
||||
t.depth(-1);
|
||||
}
|
||||
@@ -1056,12 +1057,33 @@ public final class DefaultPersister implements Persister {
|
||||
Object parentId = desc.getId(parentBean);
|
||||
List<Object> idsByParentId = many.findIdsByParentId(parentId, null, t, excludeDetailIds);
|
||||
if (!idsByParentId.isEmpty()) {
|
||||
delete(targetDesc, null, idsByParentId, t);
|
||||
deleteChildrenById(t, targetDesc, idsByParentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cascade delete child entities by Id.
|
||||
* <p>
|
||||
* Will use delete by object if the child entity has manyToMany relationships.
|
||||
*/
|
||||
private void deleteChildrenById(SpiTransaction t, BeanDescriptor<?> targetDesc, List<Object> childIds) {
|
||||
|
||||
if (targetDesc.propertiesManyToMany().length > 0) {
|
||||
// convert into a list of reference objects and perform delete by object
|
||||
List<Object> refList = new ArrayList<Object>(childIds.size());
|
||||
for (Object id : childIds) {
|
||||
refList.add(targetDesc.createReference(null, id));
|
||||
}
|
||||
deleteList(refList, t);
|
||||
|
||||
} else {
|
||||
// perform delete by statement if possible
|
||||
delete(targetDesc, null, childIds, t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save any associated one beans.
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,6 @@ 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.deploy.BeanProperty;
|
||||
import com.avaje.ebeaninternal.server.type.DataBind;
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
|
||||
import com.avaje.ebeaninternal.server.core.PstmtBatch;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
import com.avaje.ebeaninternal.server.lib.util.Str;
|
||||
import com.avaje.ebeaninternal.server.persist.BatchedPstmt;
|
||||
import com.avaje.ebeaninternal.server.persist.BatchedPstmtHolder;
|
||||
import com.avaje.ebeaninternal.server.persist.dmlbind.BindableRequest;
|
||||
@@ -56,7 +57,7 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
|
||||
this.transaction = persistRequest.getTransaction();
|
||||
this.logLevelSql = transaction.isLogSql();
|
||||
if (logLevelSql) {
|
||||
this.bindLog = new StringBuilder();
|
||||
this.bindLog = new StringBuilder(50);
|
||||
} else {
|
||||
this.bindLog = null;
|
||||
}
|
||||
@@ -137,7 +138,7 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
|
||||
protected void logSql(String sql) {
|
||||
if (logLevelSql) {
|
||||
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
|
||||
sql += "; --bind(" + bindLog + ")";
|
||||
sql = Str.add(sql, "; --bind(", bindLog.toString(), ")");
|
||||
}
|
||||
transaction.logSql(sql);
|
||||
}
|
||||
@@ -237,8 +238,7 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
|
||||
/**
|
||||
* Check with useGeneratedKeys to get appropriate PreparedStatement.
|
||||
*/
|
||||
protected PreparedStatement getPstmt(SpiTransaction t, String sql, boolean genKeys)
|
||||
throws SQLException {
|
||||
protected PreparedStatement getPstmt(SpiTransaction t, String sql, boolean genKeys) throws SQLException {
|
||||
|
||||
Connection conn = t.getInternalConnection();
|
||||
if (genKeys) {
|
||||
@@ -266,10 +266,6 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
|
||||
return stmt;
|
||||
}
|
||||
|
||||
if (logLevelSql) {
|
||||
t.logSql(sql);
|
||||
}
|
||||
|
||||
stmt = getPstmt(t, sql, genKeys);
|
||||
|
||||
PstmtBatch pstmtBatch = request.getPstmtBatch();
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.OptimisticLockException;
|
||||
@@ -115,7 +114,7 @@ public class InsertHandler extends DmlHandler {
|
||||
protected PreparedStatement getPstmt(SpiTransaction t, String sql, boolean useGeneratedKeys) throws SQLException {
|
||||
Connection conn = t.getInternalConnection();
|
||||
if (useGeneratedKeys) {
|
||||
return conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
|
||||
return conn.prepareStatement(sql, meta.getIdentityDbColumns());
|
||||
|
||||
} else {
|
||||
return conn.prepareStatement(sql);
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.avaje.ebean.bean.NodeUsageCollector;
|
||||
import com.avaje.ebean.bean.NodeUsageListener;
|
||||
import com.avaje.ebean.bean.ObjectGraphNode;
|
||||
import com.avaje.ebean.bean.PersistenceContext;
|
||||
import com.avaje.ebean.config.GlobalProperties;
|
||||
import com.avaje.ebeaninternal.api.LoadContext;
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionList;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
@@ -58,7 +59,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CQuery.class);
|
||||
|
||||
private static final int GLOBAL_ROW_LIMIT = 1000000;
|
||||
private static final int GLOBAL_ROW_LIMIT = GlobalProperties.getInt("query.globallimit",1000000);
|
||||
|
||||
/**
|
||||
* The resultSet rows read.
|
||||
@@ -187,8 +188,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
|
||||
|
||||
private final int maxRowsLimit;
|
||||
|
||||
private final PersistenceContext persistenceContext;
|
||||
|
||||
private DataReader dataReader;
|
||||
|
||||
/**
|
||||
@@ -265,7 +264,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
|
||||
this.logWhereSql = queryPlan.getLogWhereSql();
|
||||
this.desc = request.getBeanDescriptor();
|
||||
this.predicates = predicates;
|
||||
this.persistenceContext = request.getPersistenceContext();
|
||||
this.maxRowsLimit = query.getMaxRows() > 0 ? query.getMaxRows() : GLOBAL_ROW_LIMIT;
|
||||
this.help = createHelp(request);
|
||||
this.collection = (BeanCollection<T>) (help != null ? help.createEmpty(false) : null);
|
||||
@@ -359,9 +357,20 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
|
||||
SpiTransaction t = request.getTransaction();
|
||||
Connection conn = t.getInternalConnection();
|
||||
|
||||
if (query.isRawSql()) {
|
||||
ResultSet suppliedResultSet = query.getRawSql().getResultSet();
|
||||
if (suppliedResultSet != null) {
|
||||
// this is a user supplied ResultSet so use that
|
||||
dataReader = queryPlan.createDataReader(suppliedResultSet);
|
||||
bindLog = "";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (forwardOnlyHint) {
|
||||
// Use forward only hints for large resultset processing (Issue 56, MySql specific)
|
||||
pstmt = conn.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
|
||||
pstmt.setFetchSize(Integer.MIN_VALUE);
|
||||
} else {
|
||||
pstmt = conn.prepareStatement(sql);
|
||||
}
|
||||
@@ -418,7 +427,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
|
||||
* Return the persistence context.
|
||||
*/
|
||||
public PersistenceContext getPersistenceContext() {
|
||||
return persistenceContext;
|
||||
return request.getPersistenceContext();
|
||||
}
|
||||
|
||||
public void setLoadedBean(EntityBean bean, Object id, Object lazyLoadParentId) {
|
||||
@@ -441,7 +450,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
|
||||
if (lazyLoadParentId != null) {
|
||||
if (!lazyLoadParentId.equals(this.lazyLoadParentId)) {
|
||||
// get the appropriate parent bean from the persistence context
|
||||
this.lazyLoadParentBean = (EntityBean)persistenceContext.get(lazyLoadManyProperty.getBeanDescriptor().getBeanType(), lazyLoadParentId);
|
||||
this.lazyLoadParentBean = (EntityBean)getPersistenceContext().get(lazyLoadManyProperty.getBeanDescriptor().getBeanType(), lazyLoadParentId);
|
||||
this.lazyLoadParentId = lazyLoadParentId;
|
||||
}
|
||||
|
||||
@@ -477,15 +486,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasMoreRows() throws SQLException {
|
||||
synchronized (this) {
|
||||
if (cancelled) {
|
||||
return false;
|
||||
}
|
||||
return dataReader.next();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a row from the result set returning a bean.
|
||||
* <p>
|
||||
@@ -534,7 +534,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
|
||||
private boolean readBeanInternal() throws SQLException {
|
||||
|
||||
if (loadedBeanCount >= maxRowsLimit) {
|
||||
collection.setHasMoreRows(hasMoreRows());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,10 @@ public class CQueryBuilder implements Constants {
|
||||
|
||||
// always set the order by to null for row count query
|
||||
query.setOrder(null);
|
||||
if (query.isRawSql()) {
|
||||
query.setFirstRow(0);
|
||||
query.setMaxRows(0);
|
||||
}
|
||||
|
||||
ManyWhereJoins manyWhereJoins = query.getManyWhereJoins();
|
||||
|
||||
@@ -158,7 +162,7 @@ public class CQueryBuilder implements Constants {
|
||||
SqlTree sqlTree = createSqlTree(request, predicates);
|
||||
SqlLimitResponse s = buildSql(sqlSelect, request, predicates, sqlTree);
|
||||
String sql = s.getSql();
|
||||
if (hasMany) {
|
||||
if (hasMany || query.isRawSql()) {
|
||||
sql = "select count(*) from ( " + sql + ")";
|
||||
if (selectCountWithAlias) {
|
||||
sql += " as c";
|
||||
@@ -257,21 +261,21 @@ public class CQueryBuilder implements Constants {
|
||||
|
||||
ElPropertyValue el = descriptor.getElGetValue(propertyName);
|
||||
if (el == null) {
|
||||
String msg = "Property [" + propertyName + "] not found on " + descriptor.getFullName();
|
||||
throw new PersistenceException(msg);
|
||||
}
|
||||
BeanProperty beanProperty = el.getBeanProperty();
|
||||
if (beanProperty.isId()) {
|
||||
// For @Id properties we chop off the last part of the path
|
||||
propertyName = SplitName.parent(propertyName);
|
||||
} else if (beanProperty instanceof BeanPropertyAssocOne<?>) {
|
||||
String msg = "Column [" + column.getDbColumn() + "] mapped to complex Property[" + propertyName + "]";
|
||||
msg += ". It should be mapped to a simple property (proably the Id property). ";
|
||||
throw new PersistenceException(msg);
|
||||
}
|
||||
if (propertyName != null) {
|
||||
String[] pathProp = SplitName.split(propertyName);
|
||||
pathProps.addToPath(pathProp[0], pathProp[1]);
|
||||
throw new PersistenceException("Property [" + propertyName + "] not found on " + descriptor.getFullName());
|
||||
} else {
|
||||
BeanProperty beanProperty = el.getBeanProperty();
|
||||
if (beanProperty.isId() || beanProperty.isDiscriminator()) {
|
||||
// For @Id properties we chop off the last part of the path
|
||||
propertyName = SplitName.parent(propertyName);
|
||||
} else if (beanProperty instanceof BeanPropertyAssocOne<?>) {
|
||||
String msg = "Column [" + column.getDbColumn() + "] mapped to complex Property[" + propertyName + "]";
|
||||
msg += ". It should be mapped to a simple property (proably the Id property). ";
|
||||
throw new PersistenceException(msg);
|
||||
}
|
||||
if (propertyName != null) {
|
||||
String[] pathProp = SplitName.split(propertyName);
|
||||
pathProps.addToPath(pathProp[0], pathProp[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,6 +309,8 @@ public class CQueryBuilder implements Constants {
|
||||
|
||||
StringBuilder sb = new StringBuilder(500);
|
||||
|
||||
String dbOrderBy = predicates.getDbOrderBy();
|
||||
|
||||
if (selectClause != null) {
|
||||
sb.append(selectClause);
|
||||
|
||||
@@ -320,6 +326,10 @@ public class CQueryBuilder implements Constants {
|
||||
}
|
||||
|
||||
sb.append(select.getSelectSql());
|
||||
if (query.isDistinct() && dbOrderBy != null) {
|
||||
// add the orderby columns to the select clause (due to distinct)
|
||||
sb.append(", ").append(convertDbOrderByForSelect(dbOrderBy));
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(" from ");
|
||||
@@ -375,7 +385,7 @@ public class CQueryBuilder implements Constants {
|
||||
sb.append(dbFilterMany);
|
||||
}
|
||||
|
||||
String dbOrderBy = predicates.getDbOrderBy();
|
||||
|
||||
if (dbOrderBy != null) {
|
||||
sb.append(" order by ").append(dbOrderBy);
|
||||
}
|
||||
@@ -386,12 +396,20 @@ public class CQueryBuilder implements Constants {
|
||||
return sqlLimiter.limit(r);
|
||||
|
||||
} else {
|
||||
|
||||
return new SqlLimitResponse(dbPlatform.completeSql(sb.toString(), query), false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the dbOrderBy clause to be safe for adding to select. This is done when 'distinct' is
|
||||
* used.
|
||||
*/
|
||||
private String convertDbOrderByForSelect(String dbOrderBy) {
|
||||
// just remove the ASC and DESC keywords
|
||||
return dbOrderBy.replaceAll("(?i)\\b asc\\b|\\b desc\\b", "");
|
||||
}
|
||||
|
||||
private boolean isEmpty(String s) {
|
||||
return s == null || s.length() == 0;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,12 @@ public class CQueryBuilderRawSql implements Constants {
|
||||
* Build the full SQL Select statement for the request.
|
||||
*/
|
||||
public SqlLimitResponse buildSql(OrmQueryRequest<?> request, CQueryPredicates predicates, RawSql.Sql rsql) {
|
||||
|
||||
|
||||
if (rsql == null) {
|
||||
// this is a ResultSet based RawSql query - just use some placeholder for the SQL
|
||||
return new SqlLimitResponse("--ResultSetBasedRawSql", false);
|
||||
}
|
||||
|
||||
if (!rsql.isParsed()){
|
||||
String sql = rsql.getUnparsedSql();
|
||||
BindParams bindParams = request.getQuery().getBindParams();
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
import com.avaje.ebeaninternal.api.BeanIdList;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
|
||||
import com.avaje.ebeaninternal.server.lib.util.Str;
|
||||
import com.avaje.ebeaninternal.server.persist.Binder;
|
||||
import com.avaje.ebeaninternal.server.transaction.TransactionManager;
|
||||
|
||||
@@ -53,7 +54,7 @@ public class CQueryEngine {
|
||||
if (request.isLogSql()) {
|
||||
String logSql = rcQuery.getGeneratedSql();
|
||||
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
|
||||
logSql += "; --bind("+rcQuery.getBindLog()+")";
|
||||
logSql = Str.add(logSql, "; --bind(", rcQuery.getBindLog(), ")");
|
||||
}
|
||||
request.logSql(logSql);
|
||||
}
|
||||
@@ -88,7 +89,7 @@ public class CQueryEngine {
|
||||
if (request.isLogSql()) {
|
||||
String logSql = rcQuery.getGeneratedSql();
|
||||
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
|
||||
logSql += "; --bind("+rcQuery.getBindLog()+")";
|
||||
logSql= Str.add(logSql, "; --bind(", rcQuery.getBindLog(), ")");
|
||||
}
|
||||
request.logSql(logSql);
|
||||
}
|
||||
@@ -241,7 +242,7 @@ public class CQueryEngine {
|
||||
|
||||
String sql = query.getGeneratedSql();
|
||||
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
|
||||
sql += "; --bind("+query.getBindLog()+")";
|
||||
sql= Str.add(sql, "; --bind(", query.getBindLog(), ")");
|
||||
}
|
||||
query.getTransaction().logSql(sql);
|
||||
}
|
||||
|
||||
@@ -6,39 +6,49 @@ import java.util.List;
|
||||
import com.avaje.ebean.RawSql.ColumnMapping;
|
||||
import com.avaje.ebean.config.dbplatform.SqlLimitResponse;
|
||||
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
|
||||
import com.avaje.ebeaninternal.server.deploy.InheritInfo;
|
||||
import com.avaje.ebeaninternal.server.type.DataReader;
|
||||
import com.avaje.ebeaninternal.server.type.RsetDataReaderIndexed;
|
||||
|
||||
/**
|
||||
* RawSql based query plan.
|
||||
*/
|
||||
public class CQueryPlanRawSql extends CQueryPlan {
|
||||
|
||||
private final int[] rsetIndexPositions;
|
||||
|
||||
public CQueryPlanRawSql(OrmQueryRequest<?> request, SqlLimitResponse sqlRes, SqlTree sqlTree, String logWhereSql) {
|
||||
|
||||
super(request, sqlRes, sqlTree, true, logWhereSql);
|
||||
|
||||
this.rsetIndexPositions = createIndexPositions(request, sqlTree);
|
||||
private final int[] rsetIndexPositions;
|
||||
|
||||
public CQueryPlanRawSql(OrmQueryRequest<?> request, SqlLimitResponse sqlRes, SqlTree sqlTree, String logWhereSql) {
|
||||
|
||||
super(request, sqlRes, sqlTree, true, logWhereSql);
|
||||
|
||||
this.rsetIndexPositions = createIndexPositions(request, sqlTree);
|
||||
}
|
||||
|
||||
public DataReader createDataReader(ResultSet rset) {
|
||||
|
||||
return new RsetDataReaderIndexed(rset, rsetIndexPositions, isRowNumberIncluded());
|
||||
}
|
||||
|
||||
private int[] createIndexPositions(OrmQueryRequest<?> request, SqlTree sqlTree) {
|
||||
|
||||
List<String> chain = sqlTree.buildSelectExpressionChain();
|
||||
ColumnMapping columnMapping = request.getQuery().getRawSql().getColumnMapping();
|
||||
|
||||
InheritInfo inheritInfo = request.getBeanDescriptor().getInheritInfo();
|
||||
boolean addDiscriminator = inheritInfo != null;
|
||||
int offset = addDiscriminator ? 1 : 0;
|
||||
|
||||
int[] indexPositions = new int[chain.size() + offset];
|
||||
if (addDiscriminator) {
|
||||
// discriminator column must always be first in the query
|
||||
indexPositions[0] = 1;
|
||||
}
|
||||
for (int i = 0; i < chain.size(); i++) {
|
||||
String expr = chain.get(i);
|
||||
int indexPos = 1 + columnMapping.getIndexPosition(expr);
|
||||
indexPositions[i + offset] = indexPos;
|
||||
}
|
||||
|
||||
public DataReader createDataReader(ResultSet rset){
|
||||
|
||||
return new RsetDataReaderIndexed(rset, rsetIndexPositions, isRowNumberIncluded());
|
||||
}
|
||||
|
||||
|
||||
private int[] createIndexPositions(OrmQueryRequest<?> request, SqlTree sqlTree) {
|
||||
|
||||
List<String> chain = sqlTree.buildSelectExpressionChain();
|
||||
ColumnMapping columnMapping = request.getQuery().getRawSql().getColumnMapping();
|
||||
|
||||
int[] indexPositions = new int[chain.size()];
|
||||
|
||||
for (int i = 0; i < chain.size(); i++) {
|
||||
String expr = chain.get(i);
|
||||
int indexPos = 1 + columnMapping.getIndexPosition(expr);
|
||||
indexPositions[i] = indexPos;
|
||||
}
|
||||
|
||||
return indexPositions;
|
||||
}
|
||||
return indexPositions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,12 +175,11 @@ public class DefaultDbSqlContext implements DbSqlContext {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void appendFormulaJoin(String sqlFormulaJoin, boolean forceOuterJoin) {
|
||||
public void appendFormulaJoin(String sqlFormulaJoin, SqlJoinType joinType) {
|
||||
|
||||
// replace ${ta} place holder with the real table alias...
|
||||
String tableAlias = tableAliasStack.peek();
|
||||
String converted = StringHelper
|
||||
.replaceString(sqlFormulaJoin, tableAliasPlaceHolder, tableAlias);
|
||||
String converted = StringHelper.replaceString(sqlFormulaJoin, tableAliasPlaceHolder, tableAlias);
|
||||
|
||||
if (formulaJoins == null) {
|
||||
formulaJoins = new HashSet<String>();
|
||||
@@ -195,7 +194,7 @@ public class DefaultDbSqlContext implements DbSqlContext {
|
||||
formulaJoins.add(converted);
|
||||
|
||||
sb.append(" ");
|
||||
if (forceOuterJoin) {
|
||||
if (joinType == SqlJoinType.OUTER) {
|
||||
if ("join".equals(sqlFormulaJoin.substring(0, 4).toLowerCase())) {
|
||||
// prepend left outer as we are in the 'many' part
|
||||
append(" left outer ");
|
||||
|
||||
@@ -19,112 +19,107 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
|
||||
*/
|
||||
public class DefaultOrmQueryEngine implements OrmQueryEngine {
|
||||
|
||||
/**
|
||||
* Find using predicates
|
||||
*/
|
||||
private final CQueryEngine queryEngine;
|
||||
|
||||
/**
|
||||
* Create the Finder.
|
||||
*/
|
||||
public DefaultOrmQueryEngine(BeanDescriptorManager descMgr, CQueryEngine queryEngine) {
|
||||
|
||||
this.queryEngine = queryEngine;
|
||||
/**
|
||||
* Find using predicates
|
||||
*/
|
||||
private final CQueryEngine queryEngine;
|
||||
|
||||
/**
|
||||
* Create the Finder.
|
||||
*/
|
||||
public DefaultOrmQueryEngine(BeanDescriptorManager descMgr, CQueryEngine queryEngine) {
|
||||
|
||||
this.queryEngine = queryEngine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the jdbc batch by default unless explicitly turned off on the transaction.
|
||||
*/
|
||||
private <T> void flushJdbcBatchOnQuery(OrmQueryRequest<T> request) {
|
||||
|
||||
SpiTransaction t = request.getTransaction();
|
||||
if (t.isBatchFlushOnQuery()) {
|
||||
// before we perform a query, we need to flush any
|
||||
// previous persist requests that are queued/batched.
|
||||
// The query may read data affected by those requests.
|
||||
t.flushBatch();
|
||||
}
|
||||
}
|
||||
|
||||
public <T> int findRowCount(OrmQueryRequest<T> request) {
|
||||
|
||||
flushJdbcBatchOnQuery(request);
|
||||
return queryEngine.findRowCount(request);
|
||||
}
|
||||
|
||||
public <T> BeanIdList findIds(OrmQueryRequest<T> request) {
|
||||
|
||||
flushJdbcBatchOnQuery(request);
|
||||
return queryEngine.findIds(request);
|
||||
}
|
||||
|
||||
public <T> QueryIterator<T> findIterate(OrmQueryRequest<T> request) {
|
||||
|
||||
// LIMITATION: You can not use QueryIterator to load bean cache
|
||||
|
||||
flushJdbcBatchOnQuery(request);
|
||||
return queryEngine.findIterate(request);
|
||||
}
|
||||
|
||||
public <T> BeanCollection<T> findMany(OrmQueryRequest<T> request) {
|
||||
|
||||
flushJdbcBatchOnQuery(request);
|
||||
|
||||
BeanFinder<T> finder = request.getBeanFinder();
|
||||
|
||||
BeanCollection<T> result;
|
||||
if (finder != null) {
|
||||
// this bean type has its own specific finder
|
||||
result = finder.findMany(request);
|
||||
} else {
|
||||
result = queryEngine.findMany(request);
|
||||
}
|
||||
|
||||
SpiQuery<T> query = request.getQuery();
|
||||
|
||||
public <T> int findRowCount(OrmQueryRequest<T> request){
|
||||
|
||||
return queryEngine.findRowCount(request);
|
||||
if (query.isLoadBeanCache()) {
|
||||
// load the individual beans into the bean cache
|
||||
BeanDescriptor<T> descriptor = request.getBeanDescriptor();
|
||||
Collection<T> c = result.getActualDetails();
|
||||
for (T bean : c) {
|
||||
descriptor.cacheBeanPutData((EntityBean) bean);
|
||||
}
|
||||
}
|
||||
|
||||
public <T> BeanIdList findIds(OrmQueryRequest<T> request){
|
||||
|
||||
return queryEngine.findIds(request);
|
||||
if (!result.isEmpty() && query.isUseQueryCache()) {
|
||||
// load the query result into the query cache
|
||||
request.putToQueryCache(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public <T> QueryIterator<T> findIterate(OrmQueryRequest<T> request) {
|
||||
/**
|
||||
* Find a single bean using its unique id.
|
||||
*/
|
||||
public <T> T findId(OrmQueryRequest<T> request) {
|
||||
|
||||
// LIMITATION: You can not use QueryIterator to load bean cache
|
||||
|
||||
SpiTransaction t = request.getTransaction();
|
||||
|
||||
// before we perform a query, we need to flush any
|
||||
// previous persist requests that are queued/batched.
|
||||
// The query may read data affected by those requests.
|
||||
t.flushBatch();
|
||||
|
||||
return queryEngine.findIterate(request);
|
||||
}
|
||||
|
||||
public <T> BeanCollection<T> findMany(OrmQueryRequest<T> request) {
|
||||
flushJdbcBatchOnQuery(request);
|
||||
|
||||
SpiQuery<T> query = request.getQuery();
|
||||
|
||||
BeanCollection<T> result = null;
|
||||
|
||||
SpiTransaction t = request.getTransaction();
|
||||
|
||||
// before we perform a query, we need to flush any
|
||||
// previous persist requests that are queued/batched.
|
||||
// The query may read data affected by those requests.
|
||||
t.flushBatch();
|
||||
BeanFinder<T> finder = request.getBeanFinder();
|
||||
|
||||
BeanFinder<T> finder = request.getBeanFinder();
|
||||
if (finder != null) {
|
||||
// this bean type has its own specific finder
|
||||
result = finder.findMany(request);
|
||||
} else {
|
||||
result = queryEngine.findMany(request);
|
||||
}
|
||||
|
||||
if (query.isLoadBeanCache()){
|
||||
// load the individual beans into the bean cache
|
||||
BeanDescriptor<T> descriptor = request.getBeanDescriptor();
|
||||
Collection<T> c = result.getActualDetails();
|
||||
for (T bean : c) {
|
||||
descriptor.cacheBeanPutData((EntityBean)bean);
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.isEmpty() && query.isUseQueryCache()){
|
||||
// load the query result into the query cache
|
||||
request.putToQueryCache(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
T result;
|
||||
if (finder != null) {
|
||||
result = finder.find(request);
|
||||
} else {
|
||||
result = queryEngine.find(request);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find a single bean using its unique id.
|
||||
*/
|
||||
public <T> T findId(OrmQueryRequest<T> request) {
|
||||
|
||||
T result = null;
|
||||
|
||||
SpiTransaction t = request.getTransaction();
|
||||
|
||||
if (t.isBatchFlushOnQuery()){
|
||||
// before we perform a query, we need to flush any
|
||||
// previous persist requests that are queued/batched.
|
||||
// The query may read data affected by those requests.
|
||||
t.flushBatch();
|
||||
}
|
||||
|
||||
BeanFinder<T> finder = request.getBeanFinder();
|
||||
if (finder != null) {
|
||||
result = finder.find(request);
|
||||
} else {
|
||||
result = queryEngine.find(request);
|
||||
}
|
||||
|
||||
if (result != null && request.isUseBeanCache()){
|
||||
request.getBeanDescriptor().cacheBeanPutData((EntityBean)result);
|
||||
}
|
||||
|
||||
return result;
|
||||
if (result != null && request.isUseBeanCache()) {
|
||||
request.getBeanDescriptor().cacheBeanPutData((EntityBean) result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-11
@@ -22,6 +22,7 @@ import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.server.core.Message;
|
||||
import com.avaje.ebeaninternal.server.core.RelationalQueryEngine;
|
||||
import com.avaje.ebeaninternal.server.core.RelationalQueryRequest;
|
||||
import com.avaje.ebeaninternal.server.lib.util.Str;
|
||||
import com.avaje.ebeaninternal.server.persist.Binder;
|
||||
import com.avaje.ebeaninternal.server.transaction.TransactionManager;
|
||||
import com.avaje.ebeaninternal.server.type.DataBind;
|
||||
@@ -94,7 +95,7 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
|
||||
if (request.isLogSql()) {
|
||||
String logSql = sql;
|
||||
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
|
||||
logSql += "; --bind("+bindLog+")";
|
||||
logSql = Str.add(logSql, "; --bind(", bindLog, ")");
|
||||
}
|
||||
t.logSql(logSql);
|
||||
}
|
||||
@@ -115,8 +116,6 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
|
||||
maxRows = query.getMaxRows();
|
||||
}
|
||||
|
||||
boolean hasHitMaxRows = false;
|
||||
|
||||
int loadRowCount = 0;
|
||||
|
||||
SqlQueryListener listener = query.getListener();
|
||||
@@ -152,7 +151,6 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
|
||||
|
||||
if (loadRowCount == maxRows) {
|
||||
// break, as we have hit the max rows to fetch...
|
||||
hasHitMaxRows = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -160,13 +158,6 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
|
||||
|
||||
BeanCollection<?> beanColl = wrapper.getBeanCollection();
|
||||
|
||||
if (hasHitMaxRows) {
|
||||
if (rset.next()) {
|
||||
// there are more rows available after the maxRows limit
|
||||
beanColl.setHasMoreRows(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (request.isLogSummary()) {
|
||||
long exeTime = System.currentTimeMillis() - startTime;
|
||||
String msg = "SqlQuery rows[" + loadRowCount + "] time[" + exeTime + "] bind[" + bindLog + "]";
|
||||
|
||||
@@ -52,7 +52,7 @@ public class LimitOffsetPage<T> implements Page<T>, BeanCollectionTouched {
|
||||
* Perform fetch ahead when the list is first accessed.
|
||||
*/
|
||||
public void notifyTouched(BeanCollection<?> c) {
|
||||
if (c.hasMoreRows()) {
|
||||
if (hasNext()) {
|
||||
owner.fetchAheadIfRequired(pageIndex);
|
||||
}
|
||||
}
|
||||
@@ -65,9 +65,8 @@ public class LimitOffsetPage<T> implements Page<T>, BeanCollectionTouched {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean hasNext() {
|
||||
return ((BeanCollection<T>) getList()).hasMoreRows();
|
||||
return pageIndex < getTotalPageCount() - 1;
|
||||
}
|
||||
|
||||
public boolean hasPrev() {
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.avaje.ebeaninternal.server.query;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.PagedList;
|
||||
import com.avaje.ebeaninternal.api.Monitor;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
|
||||
/**
|
||||
* PagedList implementation based on limit offset types of queries.
|
||||
*
|
||||
* @param <T>
|
||||
* the entity bean type
|
||||
*/
|
||||
public class LimitOffsetPagedList<T> implements PagedList<T> {
|
||||
|
||||
private final transient EbeanServer server;
|
||||
|
||||
private final SpiQuery<T> query;
|
||||
|
||||
private final int pageSize;
|
||||
|
||||
private final int pageIndex;
|
||||
|
||||
private final Monitor monitor = new Monitor();
|
||||
|
||||
private Future<Integer> futureRowCount;
|
||||
|
||||
private List<T> list;
|
||||
|
||||
public LimitOffsetPagedList(EbeanServer server, SpiQuery<T> query, int pageIndex, int pageSize) {
|
||||
this.server = server;
|
||||
this.query = query;
|
||||
this.pageSize = pageSize;
|
||||
this.pageIndex = pageIndex;
|
||||
}
|
||||
|
||||
public void loadRowCount() {
|
||||
getFutureRowCount();
|
||||
}
|
||||
|
||||
public Future<Integer> getFutureRowCount() {
|
||||
synchronized (monitor) {
|
||||
if (futureRowCount == null) {
|
||||
futureRowCount = server.findFutureRowCount(query, null);
|
||||
}
|
||||
return futureRowCount;
|
||||
}
|
||||
}
|
||||
|
||||
public List<T> getList() {
|
||||
synchronized (monitor) {
|
||||
if (list == null) {
|
||||
query.setFirstRow(pageIndex * pageSize);
|
||||
query.setMaxRows(pageSize);
|
||||
|
||||
list = server.findList(query, null);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
public int getTotalPageCount() {
|
||||
|
||||
int rowCount = getTotalRowCount();
|
||||
if (rowCount == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return ((rowCount - 1) / pageSize) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
public int getTotalRowCount() {
|
||||
try {
|
||||
return getFutureRowCount().get();
|
||||
} catch (Exception e) {
|
||||
throw new PersistenceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return pageIndex < (getTotalPageCount() - 1);
|
||||
}
|
||||
|
||||
public boolean hasPrev() {
|
||||
return pageIndex > 0;
|
||||
}
|
||||
|
||||
public int getPageIndex() {
|
||||
return pageIndex;
|
||||
}
|
||||
|
||||
public int getPageSize() {
|
||||
return pageSize;
|
||||
}
|
||||
|
||||
public String getDisplayXtoYofZ(String to, String of) {
|
||||
|
||||
int first = pageIndex * pageSize + 1;
|
||||
int last = first + getList().size() - 1;
|
||||
int total = getTotalRowCount();
|
||||
|
||||
return first + to + last + of + total;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -56,7 +56,7 @@ public class SqlBeanLoad {
|
||||
|
||||
public Object load(BeanProperty prop) throws SQLException {
|
||||
|
||||
if (!rawSql && prop.isTransient()) {
|
||||
if (!rawSql && !prop.isLoadProperty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.avaje.ebeaninternal.server.query;
|
||||
|
||||
/**
|
||||
* Inner join, Outer join or automatic determination based on cardinality and optionality.
|
||||
*/
|
||||
public enum SqlJoinType {
|
||||
|
||||
/**
|
||||
* It is an inner join.
|
||||
*/
|
||||
INNER("join"),
|
||||
|
||||
/**
|
||||
* It is an outer join.
|
||||
*/
|
||||
OUTER("left outer join"),
|
||||
|
||||
/**
|
||||
* It is automatically determined based on cardinality and optionality.
|
||||
*/
|
||||
AUTO("JOIN-TYPE-AUTO-LITERAL-NOT-USED");
|
||||
|
||||
String literal;
|
||||
|
||||
SqlJoinType(String literal) {
|
||||
this.literal = literal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL join literal.
|
||||
*/
|
||||
public String getLiteral() {
|
||||
return literal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the actual SQL join literal taking into account the current join type and the 'default
|
||||
* join type as per deployment cardinality and optionality'.
|
||||
*/
|
||||
public String getLiteral(SqlJoinType deploymentJoinType) {
|
||||
if (this == SqlJoinType.AUTO) {
|
||||
return deploymentJoinType.getLiteral();
|
||||
}
|
||||
return this.getLiteral();
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is an AUTO join set it to OUTER as we are joining to a Many.
|
||||
*/
|
||||
public SqlJoinType autoToOuter() {
|
||||
if (this == AUTO) {
|
||||
return OUTER;
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If join is AUTO but deploymentJoinType is OUTER then go into OUTER join mode.
|
||||
*/
|
||||
public SqlJoinType autoToOuter(SqlJoinType deploymentJoinType) {
|
||||
if (this == AUTO && deploymentJoinType == OUTER) {
|
||||
return OUTER;
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,206 +18,198 @@ import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
|
||||
*/
|
||||
public class SqlTreeAlias {
|
||||
|
||||
private int counter;
|
||||
|
||||
private int manyWhereCounter;
|
||||
|
||||
private TreeSet<String> joinProps = new TreeSet<String>();
|
||||
private int counter;
|
||||
|
||||
private HashSet<String> embeddedPropertyJoins;
|
||||
private int manyWhereCounter;
|
||||
|
||||
private TreeSet<String> manyWhereJoinProps = new TreeSet<String>();
|
||||
private TreeSet<String> joinProps = new TreeSet<String>();
|
||||
|
||||
private HashMap<String,String> aliasMap = new HashMap<String,String>();
|
||||
private HashSet<String> embeddedPropertyJoins;
|
||||
|
||||
private HashMap<String, String> manyWhereAliasMap = new HashMap<String, String>();
|
||||
private TreeSet<String> manyWhereJoinProps = new TreeSet<String>();
|
||||
|
||||
private final String rootTableAlias;
|
||||
|
||||
public SqlTreeAlias(String rootTableAlias) {
|
||||
this.rootTableAlias = rootTableAlias;
|
||||
}
|
||||
private HashMap<String, String> aliasMap = new HashMap<String, String>();
|
||||
|
||||
/**
|
||||
* Add joins to support where predicates
|
||||
* @param manyWhereJoins
|
||||
*/
|
||||
public void addManyWhereJoins(Set<String> manyWhereJoins) {
|
||||
if (manyWhereJoins != null){
|
||||
for (String include : manyWhereJoins) {
|
||||
addPropertyJoin(include, manyWhereJoinProps);
|
||||
}
|
||||
}
|
||||
private HashMap<String, String> manyWhereAliasMap = new HashMap<String, String>();
|
||||
|
||||
private final String rootTableAlias;
|
||||
|
||||
public SqlTreeAlias(String rootTableAlias) {
|
||||
this.rootTableAlias = rootTableAlias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add joins to support where predicates
|
||||
*/
|
||||
public void addManyWhereJoins(Set<String> manyWhereJoins) {
|
||||
if (manyWhereJoins != null) {
|
||||
for (String include : manyWhereJoins) {
|
||||
addPropertyJoin(include, manyWhereJoinProps);
|
||||
}
|
||||
}
|
||||
|
||||
private void addEmbeddedPropertyJoin(String embProp){
|
||||
if (embeddedPropertyJoins == null){
|
||||
embeddedPropertyJoins = new HashSet<String>();
|
||||
}
|
||||
embeddedPropertyJoins.add(embProp);
|
||||
}
|
||||
|
||||
private void addEmbeddedPropertyJoin(String embProp) {
|
||||
if (embeddedPropertyJoins == null) {
|
||||
embeddedPropertyJoins = new HashSet<String>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add joins.
|
||||
*/
|
||||
public void addJoin(Set<String> propJoins, BeanDescriptor<?> desc) {
|
||||
if (propJoins != null){
|
||||
for (String propJoin : propJoins) {
|
||||
ElPropertyDeploy elProp = desc.getElPropertyDeploy(propJoin);
|
||||
if (elProp != null && elProp.getBeanProperty().isEmbedded()) {
|
||||
String[] split = SplitName.split(propJoin);
|
||||
addPropertyJoin(split[0], joinProps);
|
||||
addEmbeddedPropertyJoin(propJoin);
|
||||
|
||||
} else {
|
||||
addPropertyJoin(propJoin, joinProps);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
embeddedPropertyJoins.add(embProp);
|
||||
}
|
||||
|
||||
|
||||
private void addPropertyJoin(String include, TreeSet<String> set){
|
||||
if (set.add(include)) {
|
||||
String[] split = SplitName.split(include);
|
||||
if (split[0] != null){
|
||||
addPropertyJoin(split[0], set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a set of table alias for the given bean and fetch
|
||||
* joined properties.
|
||||
*/
|
||||
public void buildAlias() {
|
||||
|
||||
Iterator<String> i = joinProps.iterator();
|
||||
while (i.hasNext()) {
|
||||
calcAlias(i.next());
|
||||
}
|
||||
/**
|
||||
* Add joins.
|
||||
*/
|
||||
public void addJoin(Set<String> propJoins, BeanDescriptor<?> desc) {
|
||||
if (propJoins != null) {
|
||||
for (String propJoin : propJoins) {
|
||||
ElPropertyDeploy elProp = desc.getElPropertyDeploy(propJoin);
|
||||
if (elProp != null && elProp.getBeanProperty().isEmbedded()) {
|
||||
addEmbeddedPropertyJoin(propJoin);
|
||||
|
||||
i = manyWhereJoinProps.iterator();
|
||||
while (i.hasNext()) {
|
||||
calcAliasManyWhere(i.next());
|
||||
}
|
||||
|
||||
mapEmbeddedPropertyAlias();
|
||||
}
|
||||
|
||||
private void mapEmbeddedPropertyAlias() {
|
||||
if (embeddedPropertyJoins != null){
|
||||
for (String propJoin : embeddedPropertyJoins) {
|
||||
String[] split = SplitName.split(propJoin);
|
||||
// the table alias of the parent path
|
||||
String alias = getTableAlias(split[0]);
|
||||
aliasMap.put(propJoin, alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String calcAlias(String prefix) {
|
||||
|
||||
String alias = nextTableAlias();
|
||||
aliasMap.put(prefix, alias);
|
||||
return alias;
|
||||
}
|
||||
|
||||
private String calcAliasManyWhere(String prefix) {
|
||||
|
||||
String alias = nextManyWhereTableAlias();
|
||||
manyWhereAliasMap.put(prefix, alias);
|
||||
return alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the table alias for a given property name.
|
||||
*/
|
||||
public String getTableAlias(String prefix){
|
||||
if (prefix == null){
|
||||
return rootTableAlias;
|
||||
} else {
|
||||
String s = aliasMap.get(prefix);
|
||||
if (s == null){
|
||||
return calcAlias(prefix);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an alias using "Many where joins".
|
||||
*/
|
||||
public String getTableAliasManyWhere(String prefix) {
|
||||
if (prefix == null){
|
||||
return rootTableAlias;
|
||||
}
|
||||
String s = manyWhereAliasMap.get(prefix);
|
||||
if (s == null){
|
||||
s = aliasMap.get(prefix);
|
||||
}
|
||||
if (s == null) {
|
||||
String msg = "Could not determine table alias for [" + prefix + "] manyMap["
|
||||
+ manyWhereAliasMap + "] aliasMap[" + aliasMap + "]";
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse for where clauses that uses "Many where joins"
|
||||
*/
|
||||
public String parseWhere(String clause) {
|
||||
clause = parseRootAlias(clause);
|
||||
clause = parseAliasMap(clause, manyWhereAliasMap);
|
||||
return parseAliasMap(clause, aliasMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse without using any extra "Many where joins".
|
||||
*/
|
||||
public String parse(String clause) {
|
||||
clause = parseRootAlias(clause);
|
||||
return parseAliasMap(clause, aliasMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the clause replacing the table alias place holders.
|
||||
*/
|
||||
private String parseRootAlias(String clause) {
|
||||
|
||||
if (rootTableAlias == null){
|
||||
return clause.replace("${}", "");
|
||||
} else {
|
||||
return clause.replace("${}", rootTableAlias+".");
|
||||
addPropertyJoin(propJoin, joinProps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the clause replacing the table alias place holders.
|
||||
*/
|
||||
private String parseAliasMap(String clause, HashMap<String,String> parseAliasMap) {
|
||||
}
|
||||
|
||||
Iterator<Entry<String, String>> i = parseAliasMap.entrySet().iterator();
|
||||
while (i.hasNext()) {
|
||||
Map.Entry<String,String> e = i.next();
|
||||
String k = "${"+e.getKey()+"}";
|
||||
clause = clause.replace(k, e.getValue()+".");
|
||||
}
|
||||
|
||||
return clause;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the next valid table alias given the preferred table alias.
|
||||
*/
|
||||
private String nextTableAlias() {
|
||||
return "t"+(++counter);
|
||||
}
|
||||
|
||||
private String nextManyWhereTableAlias() {
|
||||
return "u"+(++manyWhereCounter);
|
||||
private void addPropertyJoin(String include, TreeSet<String> set) {
|
||||
if (set.add(include)) {
|
||||
String[] split = SplitName.split(include);
|
||||
if (split[0] != null) {
|
||||
addPropertyJoin(split[0], set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a set of table alias for the given bean and fetch joined properties.
|
||||
*/
|
||||
public void buildAlias() {
|
||||
|
||||
Iterator<String> i = joinProps.iterator();
|
||||
while (i.hasNext()) {
|
||||
calcAlias(i.next());
|
||||
}
|
||||
|
||||
i = manyWhereJoinProps.iterator();
|
||||
while (i.hasNext()) {
|
||||
calcAliasManyWhere(i.next());
|
||||
}
|
||||
|
||||
mapEmbeddedPropertyAlias();
|
||||
}
|
||||
|
||||
private void mapEmbeddedPropertyAlias() {
|
||||
if (embeddedPropertyJoins != null) {
|
||||
for (String propJoin : embeddedPropertyJoins) {
|
||||
String[] split = SplitName.split(propJoin);
|
||||
// the table alias of the parent path
|
||||
String alias = getTableAlias(split[0]);
|
||||
aliasMap.put(propJoin, alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String calcAlias(String prefix) {
|
||||
|
||||
String alias = nextTableAlias();
|
||||
aliasMap.put(prefix, alias);
|
||||
return alias;
|
||||
}
|
||||
|
||||
private String calcAliasManyWhere(String prefix) {
|
||||
|
||||
String alias = nextManyWhereTableAlias();
|
||||
manyWhereAliasMap.put(prefix, alias);
|
||||
return alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the table alias for a given property name.
|
||||
*/
|
||||
public String getTableAlias(String prefix) {
|
||||
if (prefix == null) {
|
||||
return rootTableAlias;
|
||||
} else {
|
||||
String s = aliasMap.get(prefix);
|
||||
if (s == null) {
|
||||
return calcAlias(prefix);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an alias using "Many where joins".
|
||||
*/
|
||||
public String getTableAliasManyWhere(String prefix) {
|
||||
if (prefix == null) {
|
||||
return rootTableAlias;
|
||||
}
|
||||
String s = manyWhereAliasMap.get(prefix);
|
||||
if (s == null) {
|
||||
s = aliasMap.get(prefix);
|
||||
}
|
||||
if (s == null) {
|
||||
String msg = "Could not determine table alias for [" + prefix + "] manyMap[" + manyWhereAliasMap + "] aliasMap["+ aliasMap + "]";
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse for where clauses that uses "Many where joins"
|
||||
*/
|
||||
public String parseWhere(String clause) {
|
||||
clause = parseRootAlias(clause);
|
||||
clause = parseAliasMap(clause, manyWhereAliasMap);
|
||||
return parseAliasMap(clause, aliasMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse without using any extra "Many where joins".
|
||||
*/
|
||||
public String parse(String clause) {
|
||||
clause = parseRootAlias(clause);
|
||||
return parseAliasMap(clause, aliasMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the clause replacing the table alias place holders.
|
||||
*/
|
||||
private String parseRootAlias(String clause) {
|
||||
|
||||
if (rootTableAlias == null) {
|
||||
return clause.replace("${}", "");
|
||||
} else {
|
||||
return clause.replace("${}", rootTableAlias + ".");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the clause replacing the table alias place holders.
|
||||
*/
|
||||
private String parseAliasMap(String clause, HashMap<String, String> parseAliasMap) {
|
||||
|
||||
Iterator<Entry<String, String>> i = parseAliasMap.entrySet().iterator();
|
||||
while (i.hasNext()) {
|
||||
Map.Entry<String, String> e = i.next();
|
||||
String k = "${" + e.getKey() + "}";
|
||||
clause = clause.replace(k, e.getValue() + ".");
|
||||
}
|
||||
|
||||
return clause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next valid table alias given the preferred table alias.
|
||||
*/
|
||||
private String nextTableAlias() {
|
||||
return "t" + (++counter);
|
||||
}
|
||||
|
||||
private String nextManyWhereTableAlias() {
|
||||
return "u" + (++manyWhereCounter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,11 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.avaje.ebeaninternal.api.ManyWhereJoins;
|
||||
import com.avaje.ebeaninternal.api.PropertyJoin;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery.Type;
|
||||
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
|
||||
@@ -25,9 +29,6 @@ import com.avaje.ebeaninternal.server.el.ElPropertyValue;
|
||||
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Factory for SqlTree.
|
||||
*/
|
||||
@@ -69,8 +70,7 @@ public class SqlTreeBuilder {
|
||||
/**
|
||||
* Construct for RawSql query.
|
||||
*/
|
||||
public SqlTreeBuilder(OrmQueryRequest<?> request, CQueryPredicates predicates,
|
||||
OrmQueryDetail queryDetail) {
|
||||
public SqlTreeBuilder(OrmQueryRequest<?> request, CQueryPredicates predicates, OrmQueryDetail queryDetail) {
|
||||
|
||||
this.rawSql = true;
|
||||
this.desc = request.getBeanDescriptor();
|
||||
@@ -97,7 +97,7 @@ public class SqlTreeBuilder {
|
||||
this.desc = request.getBeanDescriptor();
|
||||
this.query = request.getQuery();
|
||||
|
||||
this.subQuery = Type.SUBQUERY.equals(query.getType());
|
||||
this.subQuery = Type.SUBQUERY.equals(query.getType()) || Type.ID_LIST.equals(query.getType());
|
||||
this.includeJoin = query.getIncludeTableJoin();
|
||||
this.manyWhereJoins = query.getManyWhereJoins();
|
||||
this.queryDetail = query.getDetail();
|
||||
@@ -170,7 +170,7 @@ public class SqlTreeBuilder {
|
||||
if (rawSql) {
|
||||
return "Not Used";
|
||||
}
|
||||
rootNode.appendFrom(ctx, false);
|
||||
rootNode.appendFrom(ctx, SqlJoinType.AUTO);
|
||||
return ctx.getContent();
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ public class SqlTreeBuilder {
|
||||
if (!rawSql) {
|
||||
alias.addJoin(queryDetail.getIncludes(), desc);
|
||||
alias.addJoin(predicates.getPredicateIncludes(), desc);
|
||||
alias.addManyWhereJoins(manyWhereJoins.getJoins());
|
||||
alias.addManyWhereJoins(manyWhereJoins.getPropertyNames());
|
||||
|
||||
// build set of table alias
|
||||
alias.buildAlias();
|
||||
@@ -238,12 +238,10 @@ public class SqlTreeBuilder {
|
||||
*/
|
||||
private void addManyWhereJoins(List<SqlTreeNode> myJoinList) {
|
||||
|
||||
Set<String> includes = manyWhereJoins.getJoins();
|
||||
for (String joinProp : includes) {
|
||||
|
||||
BeanPropertyAssoc<?> beanProperty = (BeanPropertyAssoc<?>) desc
|
||||
.getBeanPropertyFromPath(joinProp);
|
||||
SqlTreeNodeManyWhereJoin nodeJoin = new SqlTreeNodeManyWhereJoin(joinProp, beanProperty);
|
||||
Collection<PropertyJoin> includes = manyWhereJoins.getPropertyJoins();
|
||||
for (PropertyJoin joinProp : includes) {
|
||||
BeanPropertyAssoc<?> beanProperty = (BeanPropertyAssoc<?>) desc.getBeanPropertyFromPath(joinProp.getProperty());
|
||||
SqlTreeNodeManyWhereJoin nodeJoin = new SqlTreeNodeManyWhereJoin(joinProp.getProperty(), beanProperty, joinProp.getSqlJoinType());
|
||||
myJoinList.add(nodeJoin);
|
||||
}
|
||||
}
|
||||
@@ -292,12 +290,11 @@ public class SqlTreeBuilder {
|
||||
// support the predicates or order by clauses.
|
||||
|
||||
// remove ManyWhereJoins from the predicateIncludes
|
||||
predicateIncludes.removeAll(manyWhereJoins.getJoins());
|
||||
predicateIncludes.removeAll(manyWhereJoins.getPropertyNames());
|
||||
|
||||
// look for predicateIncludes that are not in selectIncludes and add
|
||||
// them as extra joins to the query
|
||||
IncludesDistiller extraJoinDistill = new IncludesDistiller(desc, selectIncludes,
|
||||
predicateIncludes);
|
||||
IncludesDistiller extraJoinDistill = new IncludesDistiller(desc, selectIncludes, predicateIncludes);
|
||||
|
||||
Collection<SqlTreeNodeExtraJoin> extraJoins = extraJoinDistill.getExtraJoinRootNodes();
|
||||
if (extraJoins.isEmpty()) {
|
||||
@@ -334,8 +331,7 @@ public class SqlTreeBuilder {
|
||||
|
||||
BeanProperty p = desc.findBeanProperty(propName);
|
||||
if (p == null) {
|
||||
logger
|
||||
.error("property [" + propName + "]not found on " + desc + " for query - excluding it.");
|
||||
logger.error("property [" + propName + "]not found on " + desc + " for query - excluding it.");
|
||||
|
||||
} else if (p instanceof BeanPropertyAssoc<?> && p.isEmbedded()) {
|
||||
// if the property is embedded we need to lookup the real column name
|
||||
@@ -368,9 +364,7 @@ public class SqlTreeBuilder {
|
||||
if (!selectProps.containsProperty(baseName)) {
|
||||
BeanProperty p = desc.findBeanProperty(baseName);
|
||||
if (p == null) {
|
||||
String m = "property [" + propName + "] not found on " + desc
|
||||
+ " for query - excluding it.";
|
||||
logger.error(m);
|
||||
logger.error("property [" + propName + "] not found on " + desc + " for query - excluding it.");
|
||||
|
||||
} else if (p.isEmbedded()) {
|
||||
// add the embedded bean (and effectively
|
||||
@@ -378,8 +372,7 @@ public class SqlTreeBuilder {
|
||||
selectProps.add(p);
|
||||
|
||||
} else {
|
||||
String m = "property [" + p.getFullBeanName()
|
||||
+ "] expected to be an embedded bean for query - excluding it.";
|
||||
String m = "property [" + p.getFullBeanName() + "] expected to be an embedded bean for query - excluding it.";
|
||||
logger.error(m);
|
||||
}
|
||||
}
|
||||
@@ -389,8 +382,9 @@ public class SqlTreeBuilder {
|
||||
// sub class hierarchy if required
|
||||
BeanProperty p = desc.findBeanProperty(propName);
|
||||
if (p == null) {
|
||||
logger.error("property [" + propName + "] not found on " + desc
|
||||
+ " for query - excluding it.");
|
||||
logger.error("property [" + propName + "] not found on " + desc + " for query - excluding it.");
|
||||
p = desc.findBeanProperty("id");
|
||||
selectProps.add(p);
|
||||
|
||||
} else if (p.isId()) {
|
||||
// do not bother to include id for normal queries as the
|
||||
@@ -415,7 +409,7 @@ public class SqlTreeBuilder {
|
||||
|
||||
private SqlTreeProperties getBaseSelectPartial(BeanDescriptor<?> desc, OrmQueryProperties queryProps) {
|
||||
|
||||
SqlTreeProperties selectProps = new SqlTreeProperties(desc);
|
||||
SqlTreeProperties selectProps = new SqlTreeProperties();
|
||||
selectProps.setReadOnly(queryProps.isReadOnly());
|
||||
|
||||
// add properties in the order in which they appear
|
||||
@@ -443,7 +437,7 @@ public class SqlTreeBuilder {
|
||||
return getBaseSelectPartial(desc, queryProps);
|
||||
}
|
||||
|
||||
SqlTreeProperties selectProps = new SqlTreeProperties(desc);
|
||||
SqlTreeProperties selectProps = new SqlTreeProperties();
|
||||
selectProps.setAllProperties(true);
|
||||
|
||||
// normal simple properties of the bean
|
||||
@@ -486,9 +480,7 @@ public class SqlTreeBuilder {
|
||||
if (manyProperty != null) {
|
||||
// only one many associated allowed to be included in fetch
|
||||
if (logger.isDebugEnabled()) {
|
||||
String msg = "Not joining [" + propName + "] as already joined to a Many[" + manyProperty
|
||||
+ "].";
|
||||
logger.debug(msg);
|
||||
logger.debug("Not joining [" + propName + "] as already joined to a Many[" + manyProperty + "].");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public interface SqlTreeNode {
|
||||
/**
|
||||
* Append to the FROM part of the sql.
|
||||
*/
|
||||
public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin);
|
||||
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType);
|
||||
|
||||
/**
|
||||
* Append any where predicates for inheritance.
|
||||
|
||||
@@ -28,50 +28,47 @@ public class SqlTreeNodeBean implements SqlTreeNode {
|
||||
|
||||
private static final SqlTreeNode[] NO_CHILDREN = new SqlTreeNode[0];
|
||||
|
||||
final BeanDescriptor<?> desc;
|
||||
protected final BeanDescriptor<?> desc;
|
||||
|
||||
final IdBinder idBinder;
|
||||
protected final IdBinder idBinder;
|
||||
|
||||
/**
|
||||
* The children which will be other SelectBean or SelectProxyBean.
|
||||
*/
|
||||
final SqlTreeNode[] children;
|
||||
protected final SqlTreeNode[] children;
|
||||
|
||||
final boolean readOnlyLeaf;
|
||||
protected final boolean readOnlyLeaf;
|
||||
|
||||
/**
|
||||
* Set to true if this is a partial object fetch.
|
||||
*/
|
||||
final boolean partialObject;
|
||||
protected final boolean partialObject;
|
||||
|
||||
|
||||
final BeanProperty[] properties;
|
||||
protected final BeanProperty[] properties;
|
||||
|
||||
/**
|
||||
* Extra where clause added by Where annotation on associated many.
|
||||
*/
|
||||
final String extraWhere;
|
||||
protected final String extraWhere;
|
||||
|
||||
final BeanPropertyAssoc<?> nodeBeanProp;
|
||||
protected final BeanPropertyAssoc<?> nodeBeanProp;
|
||||
|
||||
final TableJoin[] tableJoins;
|
||||
protected final TableJoin[] tableJoins;
|
||||
|
||||
/**
|
||||
* False if report bean and has no id property.
|
||||
*/
|
||||
final boolean readId;
|
||||
protected final boolean readId;
|
||||
|
||||
final boolean disableLazyLoad;
|
||||
protected final boolean disableLazyLoad;
|
||||
|
||||
final InheritInfo inheritInfo;
|
||||
protected final InheritInfo inheritInfo;
|
||||
|
||||
final String prefix;
|
||||
|
||||
|
||||
final Map<String, String> pathMap;
|
||||
protected final String prefix;
|
||||
|
||||
protected final Map<String, String> pathMap;
|
||||
|
||||
final BeanPropertyAssocMany<?> lazyLoadParent;
|
||||
protected final BeanPropertyAssocMany<?> lazyLoadParent;
|
||||
|
||||
public SqlTreeNodeBean(String prefix, BeanPropertyAssoc<?> beanProp, SqlTreeProperties props,
|
||||
List<SqlTreeNode> myChildren, boolean withId) {
|
||||
@@ -452,20 +449,21 @@ public class SqlTreeNodeBean implements SqlTreeNode {
|
||||
/**
|
||||
* Append to the FROM clause for this node.
|
||||
*/
|
||||
public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {
|
||||
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
|
||||
|
||||
ctx.pushJoin(prefix);
|
||||
ctx.pushTableAlias(prefix);
|
||||
|
||||
forceOuterJoin = appendFromBaseTable(ctx, forceOuterJoin);
|
||||
// join and return SqlJoinType to use for child joins
|
||||
joinType = appendFromBaseTable(ctx, joinType);
|
||||
|
||||
for (int i = 0; i < properties.length; i++) {
|
||||
// usually nothing... except for 1-1 Exported
|
||||
properties[i].appendFrom(ctx, forceOuterJoin);
|
||||
properties[i].appendFrom(ctx, joinType);
|
||||
}
|
||||
|
||||
for (int i = 0; i < children.length; i++) {
|
||||
children[i].appendFrom(ctx, forceOuterJoin);
|
||||
children[i].appendFrom(ctx, joinType);
|
||||
}
|
||||
|
||||
ctx.popTableAlias();
|
||||
@@ -476,7 +474,7 @@ public class SqlTreeNodeBean implements SqlTreeNode {
|
||||
* Join to base table for this node. This includes a join to the intersection
|
||||
* table if this is a ManyToMany node.
|
||||
*/
|
||||
public boolean appendFromBaseTable(DbSqlContext ctx, boolean forceOuterJoin) {
|
||||
public SqlJoinType appendFromBaseTable(DbSqlContext ctx, SqlJoinType joinType) {
|
||||
|
||||
if (nodeBeanProp instanceof BeanPropertyAssocMany<?>) {
|
||||
BeanPropertyAssocMany<?> manyProp = (BeanPropertyAssocMany<?>) nodeBeanProp;
|
||||
@@ -488,14 +486,14 @@ public class SqlTreeNodeBean implements SqlTreeNode {
|
||||
String alias2 = alias + "z_";
|
||||
|
||||
TableJoin manyToManyJoin = manyProp.getIntersectionTableJoin();
|
||||
manyToManyJoin.addJoin(forceOuterJoin, parentAlias, alias2, ctx);
|
||||
manyToManyJoin.addJoin(joinType, parentAlias, alias2, ctx);
|
||||
|
||||
return nodeBeanProp.addJoin(forceOuterJoin, alias2, alias, ctx);
|
||||
return nodeBeanProp.addJoin(joinType, alias2, alias, ctx);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nodeBeanProp.addJoin(forceOuterJoin, prefix, ctx);
|
||||
return nodeBeanProp.addJoin(joinType, prefix, ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,13 +22,13 @@ import com.avaje.ebeaninternal.server.deploy.TableJoin;
|
||||
public class SqlTreeNodeExtraJoin implements SqlTreeNode {
|
||||
|
||||
|
||||
final BeanPropertyAssoc<?> assocBeanProperty;
|
||||
private final BeanPropertyAssoc<?> assocBeanProperty;
|
||||
|
||||
final String prefix;
|
||||
private final String prefix;
|
||||
|
||||
final boolean manyJoin;
|
||||
private final boolean manyJoin;
|
||||
|
||||
List<SqlTreeNodeExtraJoin> children;
|
||||
private List<SqlTreeNodeExtraJoin> children;
|
||||
|
||||
public SqlTreeNodeExtraJoin(String prefix, BeanPropertyAssoc<?> assocBeanProperty) {
|
||||
this.prefix = prefix;
|
||||
@@ -66,7 +66,7 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode {
|
||||
children.add(child);
|
||||
}
|
||||
|
||||
public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {
|
||||
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
|
||||
|
||||
boolean manyToMany = false;
|
||||
|
||||
@@ -82,29 +82,29 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode {
|
||||
String alias2 = alias+"z_";
|
||||
|
||||
TableJoin manyToManyJoin = manyProp.getIntersectionTableJoin();
|
||||
manyToManyJoin.addJoin(forceOuterJoin, parentAlias, alias2, ctx);
|
||||
manyToManyJoin.addJoin(joinType, parentAlias, alias2, ctx);
|
||||
|
||||
assocBeanProperty.addJoin(forceOuterJoin, alias2, alias, ctx);
|
||||
assocBeanProperty.addJoin(joinType, alias2, alias, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
if (!manyToMany){
|
||||
assocBeanProperty.addJoin(forceOuterJoin, prefix, ctx);
|
||||
assocBeanProperty.addJoin(joinType, prefix, ctx);
|
||||
}
|
||||
|
||||
if (children != null){
|
||||
|
||||
if (manyJoin){
|
||||
// make sure all decendants use OUTER JOIN
|
||||
forceOuterJoin = true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
SqlTreeNodeExtraJoin child = children.get(i);
|
||||
child.appendFrom(ctx, forceOuterJoin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (children != null) {
|
||||
|
||||
if (manyJoin) {
|
||||
// if AUTO then make all decendants use OUTER JOIN
|
||||
joinType = joinType.autoToOuter();
|
||||
}
|
||||
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
SqlTreeNodeExtraJoin child = children.get(i);
|
||||
child.appendFrom(ctx, joinType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing.
|
||||
|
||||
@@ -34,8 +34,8 @@ public final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
|
||||
* Force outer join for everything after the many property.
|
||||
*/
|
||||
@Override
|
||||
public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {
|
||||
super.appendFrom(ctx, true);
|
||||
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
|
||||
super.appendFrom(ctx, joinType.autoToOuter());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user