From c7c6410cd9387a570977c93d9d2b265ed45c4a9a Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Fri, 8 Jul 2016 22:35:04 +1200 Subject: [PATCH] #765 - ENH: Add ebean.xml with support for named RawSql, also adds EbeanServer.createNamedQuery() --- src/main/java/com/avaje/ebean/Ebean.java | 15 ++ .../java/com/avaje/ebean/EbeanServer.java | 13 ++ .../com/avaje/ebeaninternal/api/SpiQuery.java | 5 + .../server/core/DefaultServer.java | 16 ++ .../server/deploy/BeanDescriptor.java | 25 +-- .../server/deploy/BeanDescriptorManager.java | 104 ++++++++++-- .../server/deploy/BeanPropertyAssoc.java | 2 +- .../deploy/meta/DeployBeanDescriptor.java | 24 +++ .../server/deploy/parse/DeployBeanInfo.java | 11 +- .../server/query/SqlTreeNodeBean.java | 2 +- .../server/querydefn/DefaultOrmQuery.java | 9 + .../xmlmapping/XmlMappingReader.java | 27 +++ .../xmlmapping/model/ObjectFactory.java | 80 +++++++++ .../xmlmapping/model/XmAliasMapping.java | 87 ++++++++++ .../xmlmapping/model/XmColumnMapping.java | 87 ++++++++++ .../xmlmapping/model/XmEbean.java | 71 ++++++++ .../xmlmapping/model/XmEntity.java | 99 +++++++++++ .../xmlmapping/model/XmQuery.java | 61 +++++++ .../xmlmapping/model/XmRawSql.java | 160 ++++++++++++++++++ .../xmlmapping/model/package-info.java | 2 + src/main/resources/ebean-1.0.xsd | 59 +++++++ .../ebeaninternal/api/TDSpiEbeanServer.java | 5 + .../model/XmlMappingReaderTest.java | 21 +++ .../basic/TestOrderTotalAmountReportBean.java | 57 +++++++ .../tests/model/basic/OrderAggregate.java | 16 +- .../tests/rawsql/TestRawSqlOrmQuery.java | 19 +++ src/test/resources/META-INF/ebean-orm.xml | 57 ------- src/test/resources/ebean.xml | 51 ++++++ src/test/resources/orm.xml | 40 ----- src/test/resources/test-ebean.xml | 23 +++ 30 files changed, 1116 insertions(+), 132 deletions(-) create mode 100644 src/main/java/com/avaje/ebeaninternal/xmlmapping/XmlMappingReader.java create mode 100644 src/main/java/com/avaje/ebeaninternal/xmlmapping/model/ObjectFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmAliasMapping.java create mode 100644 src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmColumnMapping.java create mode 100644 src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmEbean.java create mode 100644 src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmEntity.java create mode 100644 src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmQuery.java create mode 100644 src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmRawSql.java create mode 100644 src/main/java/com/avaje/ebeaninternal/xmlmapping/model/package-info.java create mode 100644 src/main/resources/ebean-1.0.xsd create mode 100644 src/test/java/com/avaje/ebeaninternal/xmlmapping/model/XmlMappingReaderTest.java delete mode 100644 src/test/resources/META-INF/ebean-orm.xml create mode 100644 src/test/resources/ebean.xml delete mode 100644 src/test/resources/orm.xml create mode 100644 src/test/resources/test-ebean.xml diff --git a/src/main/java/com/avaje/ebean/Ebean.java b/src/main/java/com/avaje/ebean/Ebean.java index 56cb30af2..07d701dff 100644 --- a/src/main/java/com/avaje/ebean/Ebean.java +++ b/src/main/java/com/avaje/ebean/Ebean.java @@ -945,6 +945,21 @@ public final class Ebean { return serverMgr.getDefaultServer().createCsvReader(beanType); } + /** + * Create a named query. + *

+ * For RawSql the named query is expected to be in ebean.xml. + *

+ * + * @param beanType The type of entity bean + * @param namedQuery The name of the query + * @param The type of entity bean + * @return The query + */ + public static Query createNamedQuery(Class beanType, String namedQuery) { + return serverMgr.getDefaultServer().createNamedQuery(beanType, namedQuery); + } + /** * Create a query for a type of entity bean. *

diff --git a/src/main/java/com/avaje/ebean/EbeanServer.java b/src/main/java/com/avaje/ebean/EbeanServer.java index ebb69a62a..9e2d4a77a 100644 --- a/src/main/java/com/avaje/ebean/EbeanServer.java +++ b/src/main/java/com/avaje/ebean/EbeanServer.java @@ -199,6 +199,19 @@ public interface EbeanServer { */ UpdateQuery update(Class beanType); + /** + * Create a named query. + *

+ * For RawSql the named query is expected to be in ebean.xml. + *

+ * + * @param beanType The type of entity bean + * @param namedQuery The name of the query + * @param The type of entity bean + * @return The query + */ + Query createNamedQuery(Class beanType, String namedQuery); + /** * Create a query for an entity bean and synonym for {@link #find(Class)}. * diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java index 40218a10c..545101630 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -564,6 +564,11 @@ public interface SpiQuery extends Query { */ boolean tuneFetchProperties(OrmQueryDetail detail); + /** + * If this is a RawSql based entity set the default RawSql if not set. + */ + void setDefaultRawSqlIfRequired(); + /** * Set to true if this query has been tuned by autoTune. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java index 8a1934a23..1f97a70cb 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -887,6 +887,21 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return createQuery(beanType); } + @Override + public Query createNamedQuery(Class beanType, String namedQuery) { + BeanDescriptor desc = getBeanDescriptor(beanType); + if (desc == null) { + throw new PersistenceException(beanType.getName() + " is NOT an Entity Bean registered with this server?"); + } + RawSql rawSql = desc.getNamedRawSql(namedQuery); + if (rawSql != null) { + DefaultOrmQuery query = createQuery(beanType); + query.setRawSql(rawSql); + return query; + } + throw new PersistenceException("No named query called " + namedQuery + " for bean:" + beanType.getName()); + } + public DefaultOrmQuery createQuery(Class beanType) { BeanDescriptor desc = getBeanDescriptor(beanType); if (desc == null) { @@ -945,6 +960,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { private SpiOrmQueryRequest createQueryRequest(SpiQuery query, Transaction t) { + query.setDefaultRawSqlIfRequired(); if (query.isAutoTunable() && !autoTuneService.tuneQuery(query)) { // use deployment FetchType.LAZY/EAGER annotations // to define the 'default' select clause diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index face09faf..506411cea 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -109,6 +109,8 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { private final ConcurrentHashMap> comparatorCache = new ConcurrentHashMap>(); + private final Map namedRawSql; + public void merge(EntityBean bean, EntityBean existing) { EntityBeanIntercept fromEbi = bean._ebean_getIntercept(); @@ -412,6 +414,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { this.rootBeanType = PersistenceContextUtil.root(beanType); this.prototypeEntityBean = createPrototypeEntityBean(beanType); + this.namedRawSql = deploy.getNamedRawSql(); this.inheritInfo = deploy.getInheritInfo(); this.beanFinder = deploy.getBeanFinder(); @@ -443,7 +446,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { this.baseTableVersionsBetween = deploy.getBaseTableVersionsBetween(); this.dependentTables = deploy.getDependentTables(); this.dbComment = deploy.getDbComment(); - this.autoTunable = EntityType.ORM.equals(entityType) && (beanFinder == null); + this.autoTunable = EntityType.ORM == entityType && (beanFinder == null); // helper object used to derive lists of properties DeployBeanPropertyLists listHelper = new DeployBeanPropertyLists(owner, this, deploy); @@ -985,6 +988,13 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return this; } + /** + * Return the named RawSql query. + */ + public RawSql getNamedRawSql(String named) { + return namedRawSql.get(named); + } + /** * Return the type of DocStoreMode that should occur for this type of persist request * given the transactions requested mode. @@ -2169,7 +2179,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { * Return true if this is an embedded bean. */ public boolean isEmbedded() { - return EntityType.EMBEDDED.equals(entityType); + return EntityType.EMBEDDED == entityType; } /** @@ -2295,15 +2305,10 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { } /** - * Returns true if this bean is based on a table (or possibly view) and - * returns false if this bean is based on a raw sql select statement. - *

- * When false querying this bean is based on a supplied sql select statement - * placed in the orm xml file (as opposed to Ebean generated sql). - *

+ * Returns true if this bean is based on RawSql. */ - public boolean isSqlSelectBased() { - return EntityType.SQL.equals(entityType); + public boolean isRawSqlBased() { + return EntityType.SQL == entityType; } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java index 9a8983a45..4cbaa718d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.deploy; import com.avaje.ebean.BackgroundExecutor; import com.avaje.ebean.Model; +import com.avaje.ebean.RawSqlBuilder; import com.avaje.ebean.bean.BeanCollection; import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.cache.ServerCacheManager; @@ -22,10 +23,10 @@ import com.avaje.ebean.plugin.BeanType; import com.avaje.ebeaninternal.api.ConcurrencyMode; import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.api.TransactionEventTable; -import com.avaje.ebeaninternal.server.core.bootup.BootupClasses; import com.avaje.ebeaninternal.server.core.InternString; import com.avaje.ebeaninternal.server.core.InternalConfiguration; import com.avaje.ebeaninternal.server.core.Message; +import com.avaje.ebeaninternal.server.core.bootup.BootupClasses; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType; import com.avaje.ebeaninternal.server.deploy.id.IdBinder; import com.avaje.ebeaninternal.server.deploy.id.IdBinderEmbedded; @@ -47,6 +48,12 @@ import com.avaje.ebeaninternal.server.properties.BeanPropertiesReader; import com.avaje.ebeaninternal.server.properties.BeanPropertyInfo; import com.avaje.ebeaninternal.server.properties.BeanPropertyInfoFactory; import com.avaje.ebeaninternal.server.properties.EnhanceBeanPropertyInfoFactory; +import com.avaje.ebeaninternal.xmlmapping.XmlMappingReader; +import com.avaje.ebeaninternal.xmlmapping.model.XmAliasMapping; +import com.avaje.ebeaninternal.xmlmapping.model.XmColumnMapping; +import com.avaje.ebeaninternal.xmlmapping.model.XmEbean; +import com.avaje.ebeaninternal.xmlmapping.model.XmEntity; +import com.avaje.ebeaninternal.xmlmapping.model.XmRawSql; import com.avaje.ebeanservice.docstore.api.DocStoreBeanAdapter; import com.avaje.ebeanservice.docstore.api.DocStoreFactory; import org.slf4j.Logger; @@ -56,12 +63,16 @@ import javax.persistence.MappedSuperclass; import javax.persistence.PersistenceException; import javax.persistence.Transient; import javax.sql.DataSource; +import java.io.IOException; +import java.io.InputStream; import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -124,7 +135,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { private final String serverName; - private Map, DeployBeanInfo> deplyInfoMap = new HashMap, DeployBeanInfo>(); + private Map, DeployBeanInfo> deployInfoMap = new HashMap, DeployBeanInfo>(); private final Map, BeanTable> beanTableMap = new HashMap, BeanTable>(); @@ -301,6 +312,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { try { createListeners(); readEntityDeploymentInitial(); + readXmlMapping(); readEmbeddedDeployment(); readEntityBeanTable(); readEntityDeploymentAssociations(); @@ -319,8 +331,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap { logStatus(); - deplyInfoMap.clear(); - deplyInfoMap = null; + deployInfoMap.clear(); + deployInfoMap = null; return asOfTableMap; @@ -330,6 +342,62 @@ public class BeanDescriptorManager implements BeanDescriptorMap { } } + private void readXmlMapping() { + + try { + ClassLoader classLoader = serverConfig.getClassLoadConfig().getClassLoader(); + + Enumeration resources = classLoader.getResources("ebean.xml"); + + List mappings = new ArrayList(); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + InputStream is = url.openStream(); + mappings.add(XmlMappingReader.read(is)); + is.close(); + } + + for (XmEbean mapping : mappings) { + List entityDeploy = mapping.getEntity(); + for (XmEntity deploy : entityDeploy) { + readEntityMapping(classLoader, deploy); + } + } + + } catch (IOException e) { + throw new RuntimeException("Error reading ebean.xml", e); + } + } + + private void readEntityMapping(ClassLoader classLoader, XmEntity entityDeploy) { + + String entityClassName = entityDeploy.getClazz(); + Class entityClass; + try { + entityClass = Class.forName(entityClassName, false, classLoader); + } catch (Exception e) { + logger.error("Could not load entity bean class "+entityClassName+" for ebean.xml entry"); + return; + } + + DeployBeanInfo info = deployInfoMap.get(entityClass); + if (info == null) { + logger.error("No entity bean for ebean.xml entry "+entityClassName); + + } else { + for (XmRawSql sql : entityDeploy.getRawSql()) { + RawSqlBuilder builder = RawSqlBuilder.parse(sql.getQuery().getValue()); + for (XmColumnMapping columnMapping : sql.getColumnMapping()) { + builder.columnMapping(columnMapping.getColumn(), columnMapping.getProperty()); + } + for (XmAliasMapping aliasMapping : sql.getAliasMapping()) { + builder.tableAliasMapping(aliasMapping.getAlias(), aliasMapping.getProperty()); + } + info.addRawSql(sql.getName(), builder.create()); + } + } + } + /** * Return the Encrypt key given the table and column name. */ @@ -569,7 +637,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { * Return the bean deploy info for the given class. */ public DeployBeanInfo getDeploy(Class cls) { - return (DeployBeanInfo) deplyInfoMap.get(cls); + return (DeployBeanInfo) deployInfoMap.get(cls); } private void registerBeanDescriptor(BeanDescriptor desc) { @@ -601,12 +669,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap { for (Class entityClass : bootupClasses.getEntities()) { DeployBeanInfo info = createDeployBeanInfo(entityClass); - deplyInfoMap.put(entityClass, info); + deployInfoMap.put(entityClass, info); } for (Class entityClass : bootupClasses.getEmbeddables()) { DeployBeanInfo info = createDeployBeanInfo(entityClass); readDeployAssociations(info); - deplyInfoMap.put(entityClass, info); + deployInfoMap.put(entityClass, info); } } @@ -618,7 +686,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { */ private void readEntityBeanTable() { - for (DeployBeanInfo info : deplyInfoMap.values()) { + for (DeployBeanInfo info : deployInfoMap.values()) { BeanTable beanTable = createBeanTable(info); beanTableMap.put(beanTable.getBeanType(), beanTable); } @@ -632,18 +700,18 @@ public class BeanDescriptorManager implements BeanDescriptorMap { */ private void readEntityDeploymentAssociations() { - for (DeployBeanInfo info : deplyInfoMap.values()) { + for (DeployBeanInfo info : deployInfoMap.values()) { readDeployAssociations(info); } } private void readInheritedIdGenerators() { - for (DeployBeanInfo info : deplyInfoMap.values()) { + for (DeployBeanInfo info : deployInfoMap.values()) { DeployBeanDescriptor descriptor = info.getDescriptor(); InheritInfo inheritInfo = descriptor.getInheritInfo(); if (inheritInfo != null && !inheritInfo.isRoot()) { - DeployBeanInfo rootBeanInfo = deplyInfoMap.get(inheritInfo.getRoot().getType()); + DeployBeanInfo rootBeanInfo = deployInfoMap.get(inheritInfo.getRoot().getType()); PlatformIdGenerator rootIdGen = rootBeanInfo.getDescriptor().getIdGenerator(); if (rootIdGen != null) { descriptor.setIdGenerator(rootIdGen); @@ -668,20 +736,20 @@ public class BeanDescriptorManager implements BeanDescriptorMap { // We only perform 'circular' checks etc after we have // all the DeployBeanDescriptors created and in the map. - for (DeployBeanInfo info : deplyInfoMap.values()) { + for (DeployBeanInfo info : deployInfoMap.values()) { checkMappedBy(info); } - for (DeployBeanInfo info : deplyInfoMap.values()) { + for (DeployBeanInfo info : deployInfoMap.values()) { secondaryPropsJoins(info); } // Set inheritance info - for (DeployBeanInfo info : deplyInfoMap.values()) { + for (DeployBeanInfo info : deployInfoMap.values()) { setInheritanceInfo(info); } - for (DeployBeanInfo info : deplyInfoMap.values()) { + for (DeployBeanInfo info : deployInfoMap.values()) { registerBeanDescriptor(new BeanDescriptor(this, info.getDescriptor())); } } @@ -695,7 +763,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { for (DeployBeanPropertyAssocOne oneProp : info.getDescriptor().propertiesAssocOne()) { if (!oneProp.isTransient()) { - DeployBeanInfo assoc = deplyInfoMap.get(oneProp.getTargetType()); + DeployBeanInfo assoc = deployInfoMap.get(oneProp.getTargetType()); if (assoc != null) { oneProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo()); } @@ -704,7 +772,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { for (DeployBeanPropertyAssocMany manyProp : info.getDescriptor().propertiesAssocMany()) { if (!manyProp.isTransient()) { - DeployBeanInfo assoc = deplyInfoMap.get(manyProp.getTargetType()); + DeployBeanInfo assoc = deployInfoMap.get(manyProp.getTargetType()); if (assoc != null) { manyProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo()); } @@ -763,7 +831,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { private DeployBeanDescriptor getTargetDescriptor(DeployBeanPropertyAssoc prop) { Class targetType = prop.getTargetType(); - DeployBeanInfo info = deplyInfoMap.get(targetType); + DeployBeanInfo info = deployInfoMap.get(targetType); if (info == null) { String msg = "Can not find descriptor [" + targetType + "] for " + prop.getFullBeanName(); throw new PersistenceException(msg); diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java index f087c3cb3..7083e727e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java @@ -322,7 +322,7 @@ public abstract class BeanPropertyAssoc extends BeanProperty { BeanProperty idProp = target.getIdProperty(); BeanProperty[] others = target.propertiesBaseScalar(); - if (descriptor.isSqlSelectBased()) { + if (descriptor.isRawSqlBased()) { String dbColumn = owner.getDbColumn(); return new ImportedIdSimple(owner, dbColumn, idProp, 0); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java index 3fd22f4da..fdcba6adb 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.deploy.meta; +import com.avaje.ebean.RawSql; import com.avaje.ebean.annotation.Cache; import com.avaje.ebean.annotation.DocStore; import com.avaje.ebean.annotation.DocStoreMode; @@ -35,14 +36,18 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** * Describes Beans including their deployment information. */ public class DeployBeanDescriptor { + private static final Map EMPTY_RAW_MAP = new HashMap(); + private static class PropOrder implements Comparator { public int compare(DeployBeanProperty o1, DeployBeanProperty o2) { @@ -66,6 +71,8 @@ public class DeployBeanDescriptor { */ private LinkedHashMap propMap = new LinkedHashMap(); + private Map namedRawSql; + private EntityType entityType; private DeployBeanPropertyAssocOne unidirectional; @@ -1047,4 +1054,21 @@ public class DeployBeanDescriptor { if (docStorePersist != DocStoreMode.DEFAULT) return docStorePersist; return serverConfig.getDocStoreConfig().getPersist(); } + + /** + * Return the named RawSql queries. + */ + public Map getNamedRawSql() { + return (namedRawSql != null) ? namedRawSql : EMPTY_RAW_MAP; + } + + /** + * Add a named RawSql from ebean.xml file. + */ + public void addRawSql(String name, RawSql rawSql) { + if (namedRawSql == null) { + namedRawSql = new HashMap(); + } + namedRawSql.put(name, rawSql); + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployBeanInfo.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployBeanInfo.java index 1b1b58911..532ddde2d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployBeanInfo.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployBeanInfo.java @@ -1,12 +1,13 @@ package com.avaje.ebeaninternal.server.deploy.parse; -import java.util.HashMap; - +import com.avaje.ebean.RawSql; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoin; import com.avaje.ebeaninternal.server.query.SqlJoinType; +import java.util.HashMap; + /** * Wraps information about a bean during deployment parsing. */ @@ -75,4 +76,10 @@ public class DeployBeanInfo { tableJoin.setType(outerJoin ? SqlJoinType.OUTER : SqlJoinType.INNER); } + /** + * Add named RawSql from ebean.xml. + */ + public void addRawSql(String name, RawSql rawSql) { + descriptor.addRawSql(name, rawSql); + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java index 8829c10cb..acef03d9e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java @@ -128,7 +128,7 @@ public class SqlTreeNodeBean implements SqlTreeNode { // the bean has an Id property and we want to use it this.readId = withId && (desc.getIdProperty() != null); - this.disableLazyLoad = disableLazyLoad || !readId || desc.isSqlSelectBased() || temporalVersions; + this.disableLazyLoad = disableLazyLoad || !readId || desc.isRawSqlBased() || temporalVersions; this.partialObject = props.isPartialObject(); this.properties = props.getProps(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 4d74c5043..c2dec2647 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -38,6 +38,8 @@ import java.util.Set; */ public class DefaultOrmQuery implements SpiQuery { + public static final String DEFAULT_QUERY_NAME = "default"; + private final Class beanType; private final BeanDescriptor beanDescriptor; @@ -699,6 +701,13 @@ public class DefaultOrmQuery implements SpiQuery { return forUpdate; } + @Override + public void setDefaultRawSqlIfRequired() { + if (beanDescriptor.isRawSqlBased() && rawSql == null) { + rawSql = beanDescriptor.getNamedRawSql(DEFAULT_QUERY_NAME); + } + } + @Override public DefaultOrmQuery setAutoTune(boolean autoTune) { this.autoTune = autoTune; diff --git a/src/main/java/com/avaje/ebeaninternal/xmlmapping/XmlMappingReader.java b/src/main/java/com/avaje/ebeaninternal/xmlmapping/XmlMappingReader.java new file mode 100644 index 000000000..72c29da08 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/xmlmapping/XmlMappingReader.java @@ -0,0 +1,27 @@ +package com.avaje.ebeaninternal.xmlmapping; + +import com.avaje.ebeaninternal.xmlmapping.model.XmEbean; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import java.io.InputStream; + +public class XmlMappingReader { + + + /** + * Read and return a Migration from an xml document. + */ + public static XmEbean read(InputStream is) { + + try { + JAXBContext jaxbContext = JAXBContext.newInstance(XmEbean.class); + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + return (XmEbean) unmarshaller.unmarshal(is); + + } catch (JAXBException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/ObjectFactory.java b/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/ObjectFactory.java new file mode 100644 index 000000000..38151f817 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/ObjectFactory.java @@ -0,0 +1,80 @@ + +package com.avaje.ebeaninternal.xmlmapping.model; + +import javax.xml.bind.annotation.XmlRegistry; + + +/** + * This object contains factory methods for each + * Java content interface and Java element interface + * generated in the com.avaje.ebeaninternal.xmlmapping.model package. + *

An ObjectFactory allows you to programatically + * construct new instances of the Java representation + * for XML content. The Java representation of XML + * content can consist of schema derived interfaces + * and classes representing the binding of schema + * type definitions, element declarations and model + * groups. Factory methods for each of these are + * provided in this class. + * + */ +@XmlRegistry +public class ObjectFactory { + + + /** + * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.avaje.ebeaninternal.xmlmapping.model + * + */ + public ObjectFactory() { + } + + /** + * Create an instance of {@link XmRawSql } + * + */ + public XmRawSql createRawSql() { + return new XmRawSql(); + } + + /** + * Create an instance of {@link XmAliasMapping } + * + */ + public XmAliasMapping createAliasMapping() { + return new XmAliasMapping(); + } + + /** + * Create an instance of {@link XmColumnMapping } + * + */ + public XmColumnMapping createColumnMapping() { + return new XmColumnMapping(); + } + + /** + * Create an instance of {@link XmQuery } + * + */ + public XmQuery createQuery() { + return new XmQuery(); + } + + /** + * Create an instance of {@link XmEbean } + * + */ + public XmEbean createEbean() { + return new XmEbean(); + } + + /** + * Create an instance of {@link XmEntity } + * + */ + public XmEntity createEntity() { + return new XmEntity(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmAliasMapping.java b/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmAliasMapping.java new file mode 100644 index 000000000..d28e745c8 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmAliasMapping.java @@ -0,0 +1,87 @@ + +package com.avaje.ebeaninternal.xmlmapping.model; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType>
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <attribute name="alias" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *       <attribute name="property" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "") +@XmlRootElement(name = "alias-mapping") +public class XmAliasMapping { + + @XmlAttribute(name = "alias", required = true) + protected String alias; + @XmlAttribute(name = "property", required = true) + protected String property; + + /** + * Gets the value of the alias property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getAlias() { + return alias; + } + + /** + * Sets the value of the alias property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setAlias(String value) { + this.alias = value; + } + + /** + * Gets the value of the property property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getProperty() { + return property; + } + + /** + * Sets the value of the property property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setProperty(String value) { + this.property = value; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmColumnMapping.java b/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmColumnMapping.java new file mode 100644 index 000000000..f00f8664d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmColumnMapping.java @@ -0,0 +1,87 @@ + +package com.avaje.ebeaninternal.xmlmapping.model; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType>
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <attribute name="column" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *       <attribute name="property" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "") +@XmlRootElement(name = "column-mapping") +public class XmColumnMapping { + + @XmlAttribute(name = "column", required = true) + protected String column; + @XmlAttribute(name = "property", required = true) + protected String property; + + /** + * Gets the value of the column property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getColumn() { + return column; + } + + /** + * Sets the value of the column property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setColumn(String value) { + this.column = value; + } + + /** + * Gets the value of the property property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getProperty() { + return property; + } + + /** + * Sets the value of the property property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setProperty(String value) { + this.property = value; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmEbean.java b/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmEbean.java new file mode 100644 index 000000000..b0fb313b2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmEbean.java @@ -0,0 +1,71 @@ + +package com.avaje.ebeaninternal.xmlmapping.model; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType>
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element ref="{http://ebean-orm.github.io/xml/ns/ebean}entity" maxOccurs="unbounded"/>
+ *       </sequence>
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + "entity" +}) +@XmlRootElement(name = "ebean") +public class XmEbean { + + @XmlElement(required = true) + protected List entity; + + /** + * Gets the value of the entity property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the entity property. + * + *

+ * For example, to add a new item, do as follows: + *

+     *    getEntity().add(newItem);
+     * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link XmEntity } + * + * + */ + public List getEntity() { + if (entity == null) { + entity = new ArrayList(); + } + return this.entity; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmEntity.java b/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmEntity.java new file mode 100644 index 000000000..c7779e48f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmEntity.java @@ -0,0 +1,99 @@ + +package com.avaje.ebeaninternal.xmlmapping.model; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType>
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element ref="{http://ebean-orm.github.io/xml/ns/ebean}raw-sql" maxOccurs="unbounded"/>
+ *       </sequence>
+ *       <attribute name="class" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + "rawSql" +}) +@XmlRootElement(name = "entity") +public class XmEntity { + + @XmlElement(name = "raw-sql", required = true) + protected List rawSql; + @XmlAttribute(name = "class", required = true) + protected String clazz; + + /** + * Gets the value of the rawSql property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the rawSql property. + * + *

+ * For example, to add a new item, do as follows: + *

+     *    getRawSql().add(newItem);
+     * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link XmRawSql } + * + * + */ + public List getRawSql() { + if (rawSql == null) { + rawSql = new ArrayList(); + } + return this.rawSql; + } + + /** + * Gets the value of the clazz property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getClazz() { + return clazz; + } + + /** + * Sets the value of the clazz property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setClazz(String value) { + this.clazz = value; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmQuery.java b/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmQuery.java new file mode 100644 index 000000000..f331726ed --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmQuery.java @@ -0,0 +1,61 @@ + +package com.avaje.ebeaninternal.xmlmapping.model; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.XmlValue; + + +/** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType>
+ *   <simpleContent>
+ *     <extension base="<http://www.w3.org/2001/XMLSchema>string">
+ *     </extension>
+ *   </simpleContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + "value" +}) +@XmlRootElement(name = "query") +public class XmQuery { + + @XmlValue + protected String value; + + /** + * Gets the value of the value property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getValue() { + return value; + } + + /** + * Sets the value of the value property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setValue(String value) { + this.value = value; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmRawSql.java b/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmRawSql.java new file mode 100644 index 000000000..ec79bdb66 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/XmRawSql.java @@ -0,0 +1,160 @@ + +package com.avaje.ebeaninternal.xmlmapping.model; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType>
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element ref="{http://ebean-orm.github.io/xml/ns/ebean}alias-mapping" maxOccurs="unbounded" minOccurs="0"/>
+ *         <element ref="{http://ebean-orm.github.io/xml/ns/ebean}column-mapping" maxOccurs="unbounded" minOccurs="0"/>
+ *         <element ref="{http://ebean-orm.github.io/xml/ns/ebean}query"/>
+ *       </sequence>
+ *       <attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + "aliasMapping", + "columnMapping", + "query" +}) +@XmlRootElement(name = "raw-sql") +public class XmRawSql { + + @XmlElement(name = "alias-mapping") + protected List aliasMapping; + @XmlElement(name = "column-mapping") + protected List columnMapping; + @XmlElement(required = true) + protected XmQuery query; + @XmlAttribute(name = "name", required = true) + protected String name; + + /** + * Gets the value of the aliasMapping property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the aliasMapping property. + * + *

+ * For example, to add a new item, do as follows: + *

+     *    getAliasMapping().add(newItem);
+     * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link XmAliasMapping } + * + * + */ + public List getAliasMapping() { + if (aliasMapping == null) { + aliasMapping = new ArrayList(); + } + return this.aliasMapping; + } + + /** + * Gets the value of the columnMapping property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the columnMapping property. + * + *

+ * For example, to add a new item, do as follows: + *

+     *    getColumnMapping().add(newItem);
+     * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link XmColumnMapping } + * + * + */ + public List getColumnMapping() { + if (columnMapping == null) { + columnMapping = new ArrayList(); + } + return this.columnMapping; + } + + /** + * Gets the value of the query property. + * + * @return + * possible object is + * {@link XmQuery } + * + */ + public XmQuery getQuery() { + return query; + } + + /** + * Sets the value of the query property. + * + * @param value + * allowed object is + * {@link XmQuery } + * + */ + public void setQuery(XmQuery value) { + this.query = value; + } + + /** + * Gets the value of the name property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getName() { + return name; + } + + /** + * Sets the value of the name property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setName(String value) { + this.name = value; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/package-info.java b/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/package-info.java new file mode 100644 index 000000000..88e0fda8f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/xmlmapping/model/package-info.java @@ -0,0 +1,2 @@ +@javax.xml.bind.annotation.XmlSchema(namespace = "http://ebean-orm.github.io/xml/ns/ebean", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED) +package com.avaje.ebeaninternal.xmlmapping.model; diff --git a/src/main/resources/ebean-1.0.xsd b/src/main/resources/ebean-1.0.xsd new file mode 100644 index 000000000..970e3c605 --- /dev/null +++ b/src/main/resources/ebean-1.0.xsd @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java index 15408af46..1273014ad 100644 --- a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java +++ b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java @@ -285,6 +285,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer { return null; } + @Override + public Query createNamedQuery(Class beanType, String namedQuery) { + return null; + } + @Override public Query createQuery(Class beanType) { return null; diff --git a/src/test/java/com/avaje/ebeaninternal/xmlmapping/model/XmlMappingReaderTest.java b/src/test/java/com/avaje/ebeaninternal/xmlmapping/model/XmlMappingReaderTest.java new file mode 100644 index 000000000..72371a959 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/xmlmapping/model/XmlMappingReaderTest.java @@ -0,0 +1,21 @@ +package com.avaje.ebeaninternal.xmlmapping.model; + +import com.avaje.ebeaninternal.xmlmapping.XmlMappingReader; +import org.junit.Test; + +import java.io.InputStream; + +import static org.junit.Assert.assertNotNull; + +public class XmlMappingReaderTest { + + @Test + public void read() throws Exception { + + InputStream is = XmlMappingReaderTest.class.getResourceAsStream("/test-ebean.xml"); + XmEbean testMapping = XmlMappingReader.read(is); + + assertNotNull(testMapping); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/tests/basic/TestOrderTotalAmountReportBean.java b/src/test/java/com/avaje/tests/basic/TestOrderTotalAmountReportBean.java index ddcf8cf98..b7860faaf 100644 --- a/src/test/java/com/avaje/tests/basic/TestOrderTotalAmountReportBean.java +++ b/src/test/java/com/avaje/tests/basic/TestOrderTotalAmountReportBean.java @@ -14,6 +14,7 @@ import com.avaje.tests.model.basic.OrderAggregate; import com.avaje.tests.model.basic.ResetBasicData; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertNotNull; public class TestOrderTotalAmountReportBean extends BaseTestCase { @@ -73,5 +74,61 @@ public class TestOrderTotalAmountReportBean extends BaseTestCase { assertThat(query.getGeneratedSql()).contains("count(*) as total_items, sum(order_qty*unit_price) as total_amount"); } + @Test + public void testDefaultNamedRawSql() { + Query query = Ebean.find(OrderAggregate.class); + List list = query.findList(); + + assertThat(query.getGeneratedSql()).contains("count(*) as total_items, sum(order_qty*unit_price) as total_amount"); + assertNotNull(list); + } + + @Test + public void testNamedRawSql() { + + ResetBasicData.reset(); + + Query query = Ebean.getDefaultServer().createNamedQuery(OrderAggregate.class, "withMax"); + List list = query.findList(); + + assertThat(query.getGeneratedSql()).contains("count(*) as totalItems, sum(order_qty*unit_price) as totalAmount, max(order_qty*unit_price) as maxAmount from o_order_detail"); + assertNotNull(list); + } + + @Test + public void testNamedRawSql_with_extraPredicates() { + + ResetBasicData.reset(); + + Query query = Ebean.getDefaultServer().createNamedQuery(OrderAggregate.class, "withMax"); + List list = query + .where().gt("order.id", 1) + .having().gt("totalItems", 1) + .order().desc("totalAmount") + .findList(); + + assertThat(query.getGeneratedSql()).contains("count(*) as totalItems, sum(order_qty*unit_price) as totalAmount, max(order_qty*unit_price) as maxAmount from o_order_detail"); + assertThat(query.getGeneratedSql()).contains("from o_order_detail where order_id > ? group by order_id having count(*) > ? order by sum(order_qty*unit_price) desc"); + assertNotNull(list); + } + + @Test + public void testNamedRawSql_with_param() { + + ResetBasicData.reset(); + + Query query = Ebean.getDefaultServer().createNamedQuery(OrderAggregate.class, "withParam"); + List list = query + .setParameter("minId", 2) + .where().isNotNull("order.id") + .having().lt("totalAmount", 100) + .order().desc("totalAmount") + .setMaxRows(10) + .findList(); + + assertThat(query.getGeneratedSql()).contains("count(*) as totalItems, sum(order_qty*unit_price) as totalAmount, max(order_qty*unit_price) as maxAmount from o_order_detail"); + assertThat(query.getGeneratedSql()).contains("from o_order_detail where id > ? and order_id is not null group by order_id having sum(order_qty*unit_price) < ? order by sum(order_qty*unit_price) desc"); + assertNotNull(list); + } } diff --git a/src/test/java/com/avaje/tests/model/basic/OrderAggregate.java b/src/test/java/com/avaje/tests/model/basic/OrderAggregate.java index f22b95d77..b64c6e887 100644 --- a/src/test/java/com/avaje/tests/model/basic/OrderAggregate.java +++ b/src/test/java/com/avaje/tests/model/basic/OrderAggregate.java @@ -19,9 +19,11 @@ public class OrderAggregate { @OneToOne Order order; + Double maxAmount; + Double totalAmount; - Double totalItems; + Long totalItems; public String toString() { return order.getId() + " totalAmount:" + totalAmount + " totalItems:" + totalItems; @@ -43,11 +45,19 @@ public class OrderAggregate { this.totalAmount = totalAmount; } - public Double getTotalItems() { + public Long getTotalItems() { return totalItems; } - public void setTotalItems(Double totalItems) { + public void setTotalItems(Long totalItems) { this.totalItems = totalItems; } + + public Double getMaxAmount() { + return maxAmount; + } + + public void setMaxAmount(Double maxAmount) { + this.maxAmount = maxAmount; + } } diff --git a/src/test/java/com/avaje/tests/rawsql/TestRawSqlOrmQuery.java b/src/test/java/com/avaje/tests/rawsql/TestRawSqlOrmQuery.java index 9f1b23097..3ae69818f 100644 --- a/src/test/java/com/avaje/tests/rawsql/TestRawSqlOrmQuery.java +++ b/src/test/java/com/avaje/tests/rawsql/TestRawSqlOrmQuery.java @@ -22,6 +22,25 @@ import static org.assertj.core.api.Assertions.assertThat; public class TestRawSqlOrmQuery extends BaseTestCase { + @Test + public void testNamed() { + + ResetBasicData.reset(); + + Query query = Ebean.createNamedQuery(Order.class, "myRawTest"); + query.setParameter("orderStatus", Order.Status.NEW); + query.setMaxRows(10); + List list = query.findList(); + for (Order order : list) { + order.getCretime(); + } + + String sql = query.getGeneratedSql(); + assertThat(sql).contains("select o.id, o.status, o.ship_date, c.id, c.name, a.id, a.line_1, a.line_2, a.city from o_order o"); + assertThat(sql).contains("join o_customer c on o.kcustomer_id = c.id "); + assertThat(sql).contains("where o.status = ? order by c.name, c.id"); + } + @Test public void test() { diff --git a/src/test/resources/META-INF/ebean-orm.xml b/src/test/resources/META-INF/ebean-orm.xml deleted file mode 100644 index e2c1407ec..000000000 --- a/src/test/resources/META-INF/ebean-orm.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - select order_id, count(*) as totalItems, sum(order_qty*unit_price) as totalAmount - from o_order_detail - group by order_id - - - - - - - select order_id, sum(order_qty*unit_price) as totalAmount - from o_order_detail - group by order_id - - - - - select order_id, count(*) as total_items, sum(order_qty*unit_price) as total_amount - from o_order_detail - group by order_id - - - - - select order_id, sum(order_qty*unit_price) as total_amount - from o_order_detail - group by order_id - - - - - - - - - - select id, name, 12 as myint from t_mapsuper1 - - - - - - - - - select order_id, count(*) as detailCount from o_order_detail group by order_id - - - - - \ No newline at end of file diff --git a/src/test/resources/ebean.xml b/src/test/resources/ebean.xml new file mode 100644 index 000000000..8dcbf7c49 --- /dev/null +++ b/src/test/resources/ebean.xml @@ -0,0 +1,51 @@ + + + + + + + + + + select order_id, count(*) as total_items, sum(order_qty*unit_price) as total_amount + from o_order_detail + group by order_id + + + + + + + select order_id, count(*) as totalItems, sum(order_qty*unit_price) as totalAmount, max(order_qty*unit_price) as maxAmount + from o_order_detail + group by order_id + + + + + + + select order_id, count(*) as totalItems, sum(order_qty*unit_price) as totalAmount, max(order_qty*unit_price) as maxAmount + from o_order_detail where id > :minId + group by order_id + + + + + + + + + + + select o.id, o.status, o.ship_date, c.id, c.name, a.id, a.line_1, a.line_2, a.city + from o_order o + join o_customer c on o.kcustomer_id = c.id + join o_address a on c.billing_address_id = a.id + where o.status = :orderStatus + order by c.name, c.id + + + + + diff --git a/src/test/resources/orm.xml b/src/test/resources/orm.xml deleted file mode 100644 index 54bcd91d0..000000000 --- a/src/test/resources/orm.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - -com.avaje.tests.model.basic - - - - - find order - join customer (name) - join customer.billingAddress (*) - join details - join details.product (sku, name) - order by customer.name asc - limit 10 - - - - - find order - join customer (name) - join customer.billingAddress (*) - join details - join details.product (sku, name) - order by customer.name asc - limit 10 - - - - - - - select * from o_order where id = :id - - - - - \ No newline at end of file diff --git a/src/test/resources/test-ebean.xml b/src/test/resources/test-ebean.xml new file mode 100644 index 000000000..d28c6ab52 --- /dev/null +++ b/src/test/resources/test-ebean.xml @@ -0,0 +1,23 @@ + + + + + + + + select order_id, count(*) as totalItems, sum(order_qty*unit_price) as totalAmount + from o_order_detail + group by order_id + + + + + + select order_id, count(*) as totalItems, sum(order_qty*unit_price) as totalAmount + from o_order_detail + group by order_id + + + + +