From 5575273c96fd3b49f876814045a0fabf8e467f23 Mon Sep 17 00:00:00 2001 From: yuanxuegui Date: Mon, 19 Mar 2018 18:04:49 +0800 Subject: [PATCH] ENH: Add named DtoQuery, such that we can define native SQL queries in ebean mapping xml for DtoQuery. --- src/main/java/io/ebean/EbeanServer.java | 25 +- .../server/core/DefaultServer.java | 25 +- .../server/core/InternalConfiguration.java | 51 ++- .../server/deploy/BeanDescriptorManager.java | 87 +---- .../server/dto/DtoBeanDescriptor.java | 62 +++- .../server/dto/DtoBeanManager.java | 55 ++- .../xmlmapping/XmlMappingReader.java | 40 ++- .../ebeaninternal/xmlmapping/model/XmDto.java | 121 +++++++ .../xmlmapping/model/XmEbean.java | 19 +- src/main/resources/ebean-1.0.xsd | 11 + src/test/java/io/ebean/NamedDtoQueryTest.java | 335 ++++++++++++++++++ .../ebeaninternal/api/TDSpiEbeanServer.java | 10 +- .../ebean-xml-mappings/DCustMapping.xml | 77 ++++ 13 files changed, 814 insertions(+), 104 deletions(-) create mode 100644 src/main/java/io/ebeaninternal/xmlmapping/model/XmDto.java create mode 100644 src/test/java/io/ebean/NamedDtoQueryTest.java create mode 100644 src/test/resources/ebean-xml-mappings/DCustMapping.xml diff --git a/src/main/java/io/ebean/EbeanServer.java b/src/main/java/io/ebean/EbeanServer.java index f13cb86e7..78619886b 100644 --- a/src/main/java/io/ebean/EbeanServer.java +++ b/src/main/java/io/ebean/EbeanServer.java @@ -8,12 +8,6 @@ import io.ebean.plugin.Property; import io.ebean.plugin.SpiServer; import io.ebean.text.csv.CsvReader; import io.ebean.text.json.JsonContext; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.persistence.NonUniqueResultException; -import javax.persistence.OptimisticLockException; -import javax.persistence.PersistenceException; import java.util.Collection; import java.util.List; import java.util.Map; @@ -22,6 +16,11 @@ import java.util.Set; import java.util.concurrent.Callable; import java.util.function.Consumer; import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.persistence.NonUniqueResultException; +import javax.persistence.OptimisticLockException; +import javax.persistence.PersistenceException; /** * Provides the API for fetching and saving beans to a particular DataSource. @@ -105,6 +104,7 @@ public interface EbeanServer { * * @param shutdownDataSource if true then shutdown the underlying DataSource if it is the EbeanORM * DataSource implementation. + * DataSource implementation. * @param deregisterDriver if true then deregister the JDBC driver if it is the EbeanORM * DataSource implementation. */ @@ -437,6 +437,19 @@ public interface EbeanServer { */ DtoQuery findDto(Class dtoType, String sql); + /** + * Create a named Query for DTO beans. + *

+ * DTO beans are just normal bean like classes with public constructor(s) and setters. + * They do not need to be registered with Ebean before use. + *

+ * + * @param dtoType The type of the DTO bean the rows will be mapped into. + * @param namedQuery The name of the query + * @param The type of the DTO bean. + */ + DtoQuery createNamedDtoQuery(Class dtoType, String namedQuery); + /** * Create a SqlQuery for executing native sql * query statements. diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 7a75abd5f..da3d5af00 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -105,13 +105,6 @@ import io.ebeaninternal.server.transaction.TransactionManager; import io.ebeaninternal.util.ParamTypeHelper; import io.ebeaninternal.util.ParamTypeHelper.TypeInfo; import io.ebeanservice.docstore.api.DocStoreIntegration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.persistence.NonUniqueResultException; -import javax.persistence.OptimisticLockException; -import javax.persistence.PersistenceException; -import javax.sql.DataSource; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -126,6 +119,12 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; +import javax.persistence.NonUniqueResultException; +import javax.persistence.OptimisticLockException; +import javax.persistence.PersistenceException; +import javax.sql.DataSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The default server side implementation of EbeanServer. @@ -966,7 +965,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public Query createQuery(Class beanType, String eql) { + public DefaultOrmQuery createQuery(Class beanType, String eql) { DefaultOrmQuery query = createQuery(beanType); EqlParser.parse(eql, query); return query; @@ -999,6 +998,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return new DefaultDtoQuery<>(this, descriptor, sql.trim()); } + @Override + public DtoQuery createNamedDtoQuery(Class dtoType, String namedQuery) { + DtoBeanDescriptor descriptor = dtoBeanManager.getDescriptor(dtoType); + SpiRawSql rawSql = descriptor.getNamedRawSql(namedQuery); + if (rawSql != null) { + return new DefaultDtoQuery<>(this, descriptor, rawSql.getSql().getUnparsedSql()); + } + throw new PersistenceException("No named query called " + namedQuery + " for bean:" + dtoType.getName()); + } + @Override public DtoQuery findDto(Class dtoType, SpiQuery ormQuery) { diff --git a/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java index ab0587d94..82d395aed 100644 --- a/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java +++ b/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java @@ -69,19 +69,23 @@ import io.ebeaninternal.server.transaction.TransactionManagerOptions; import io.ebeaninternal.server.transaction.TransactionScopeManager; import io.ebeaninternal.server.type.DefaultTypeManager; import io.ebeaninternal.server.type.TypeManager; +import io.ebeaninternal.xmlmapping.XmlMappingReader; +import io.ebeaninternal.xmlmapping.model.XmEbean; import io.ebeanservice.docstore.api.DocStoreFactory; import io.ebeanservice.docstore.api.DocStoreIntegration; import io.ebeanservice.docstore.api.DocStoreUpdateProcessor; import io.ebeanservice.docstore.none.NoneDocStoreFactory; -import org.avaje.datasource.DataSourcePool; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.sql.DataSource; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.ServiceLoader; +import javax.sql.DataSource; +import org.avaje.classpath.scanner.ClassPathScanner; +import org.avaje.classpath.scanner.Resource; +import org.avaje.classpath.scanner.ResourceFilter; +import org.avaje.datasource.DataSourcePool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Used to extend the ServerConfig with additional objects used to configure and @@ -134,6 +138,8 @@ public class InternalConfiguration { private final MultiValueBind multiValueBind; + private List xmlEbeanList = new ArrayList<>(); + public InternalConfiguration(ClusterManager clusterManager, SpiCacheManager cacheManager, SpiBackgroundExecutor backgroundExecutor, ServerConfig serverConfig, BootupClasses bootupClasses) { @@ -153,12 +159,20 @@ public class InternalConfiguration { this.multiValueBind = createMultiValueBind(databasePlatform.getPlatform()); this.deployInherit = new DeployInherit(bootupClasses); + xmlEbeanList = XmlMappingReader.readByResourceName(serverConfig.getClassLoadConfig().getClassLoader(), + "ebean.xml"); + List searchXmlMapping = searchXmlMapping(); + xmlEbeanList.addAll(XmlMappingReader.readByResourceList(serverConfig.getClassLoadConfig().getClassLoader(), + searchXmlMapping)); + this.deployCreateProperties = new DeployCreateProperties(typeManager); this.deployUtil = new DeployUtil(typeManager, serverConfig); this.dtoBeanManager = new DtoBeanManager(typeManager); + dtoBeanManager.readXmlMapping(serverConfig.getClassLoadConfig().getClassLoader(), + xmlEbeanList); this.beanDescriptorManager = new BeanDescriptorManager(this); - Map asOfTableMapping = beanDescriptorManager.deploy(); + Map asOfTableMapping = beanDescriptorManager.deploy(xmlEbeanList); Map draftTableMap = beanDescriptorManager.getDraftTableMap(); beanDescriptorManager.scheduleBackgroundTrim(); @@ -167,6 +181,31 @@ public class InternalConfiguration { this.cQueryEngine = new CQueryEngine(serverConfig, databasePlatform, binder, asOfTableMapping, draftTableMap); } + private List searchXmlMapping() { + List scanners = ClassPathScanners.find(serverConfig); + List mappingLocations = serverConfig.getMappingLocations(); + List resourceList = new ArrayList<>(); + + long st = System.currentTimeMillis(); + if (mappingLocations != null && !mappingLocations.isEmpty()) { + for (ClassPathScanner finder : scanners) { + for (String mappingLocation : mappingLocations) { + resourceList.addAll(finder.scanForResources(mappingLocation, new ResourceFilter() { + + @Override + public boolean isMatch(String resourceName) { + return resourceName.endsWith(".xml"); + } + })); + } + } + } + + long searchTime = System.currentTimeMillis() - st; + logger.debug("Classpath search mappings[{}] searchTime[{}]", resourceList.size(), searchTime); + return resourceList; + } + /** * Create and return the ExpressionFactory based on configuration and database platform. */ diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java index 1f37d27b3..0aba19bb6 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -26,7 +26,6 @@ import io.ebeaninternal.api.ConcurrencyMode; import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.api.TransactionEventTable; import io.ebeaninternal.server.cache.SpiCacheManager; -import io.ebeaninternal.server.core.ClassPathScanners; import io.ebeaninternal.server.core.InternString; import io.ebeaninternal.server.core.InternalConfiguration; import io.ebeaninternal.server.core.Message; @@ -57,7 +56,6 @@ import io.ebeaninternal.server.query.CQueryPlan; import io.ebeaninternal.server.type.ScalarType; import io.ebeaninternal.server.type.ScalarTypeInteger; import io.ebeaninternal.server.type.TypeManager; -import io.ebeaninternal.xmlmapping.XmlMappingReader; import io.ebeaninternal.xmlmapping.model.XmAliasMapping; import io.ebeaninternal.xmlmapping.model.XmColumnMapping; import io.ebeaninternal.xmlmapping.model.XmEbean; @@ -66,27 +64,12 @@ import io.ebeaninternal.xmlmapping.model.XmNamedQuery; import io.ebeaninternal.xmlmapping.model.XmRawSql; import io.ebeanservice.docstore.api.DocStoreBeanAdapter; import io.ebeanservice.docstore.api.DocStoreFactory; -import org.avaje.classpath.scanner.ClassPathScanner; -import org.avaje.classpath.scanner.Resource; -import org.avaje.classpath.scanner.ResourceFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.persistence.MappedSuperclass; -import javax.persistence.PersistenceException; -import javax.persistence.Transient; -import javax.sql.DataSource; -import java.io.FileInputStream; -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; @@ -94,6 +77,12 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import javax.persistence.MappedSuperclass; +import javax.persistence.PersistenceException; +import javax.persistence.Transient; +import javax.sql.DataSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Creates BeanDescriptors. @@ -370,12 +359,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap { /** * Deploy returning the asOfTableMap (which is required by the SQL builders). */ - public Map deploy() { + public Map deploy(List mappings) { try { createListeners(); readEntityDeploymentInitial(); - readXmlMapping(); + readXmlMapping(mappings); readEmbeddedDeployment(); readEntityBeanTable(); readEntityDeploymentAssociations(); @@ -406,63 +395,15 @@ public class BeanDescriptorManager implements BeanDescriptorMap { } } - private void readXmlMapping() { + private void readXmlMapping(List mappings) { + ClassLoader classLoader = serverConfig.getClassLoadConfig().getClassLoader(); - try { - ClassLoader classLoader = serverConfig.getClassLoadConfig().getClassLoader(); - - Enumeration resources = classLoader.getResources("ebean.xml"); - - List mappings = new ArrayList<>(); - while (resources.hasMoreElements()) { - URL url = resources.nextElement(); - try (InputStream is = url.openStream()) { - mappings.add(XmlMappingReader.read(is)); - } - } - - List xmlMappingResources = searchXmlMapping(); - for (Resource xmlMappingRes : xmlMappingResources) { - try (InputStream is = new FileInputStream(xmlMappingRes.getLocationOnDisk())) { - mappings.add(XmlMappingReader.read(is)); - } - } - - 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 mapping", e); - } - } - - private List searchXmlMapping() { - List scanners = ClassPathScanners.find(serverConfig); - List mappingLocations = serverConfig.getMappingLocations(); - List resourceList = new ArrayList<>(); - - long st = System.currentTimeMillis(); - if (mappingLocations != null && !mappingLocations.isEmpty()) { - for (ClassPathScanner finder : scanners) { - for (String mappingLocation : mappingLocations) { - resourceList.addAll(finder.scanForResources(mappingLocation, new ResourceFilter() { - - @Override - public boolean isMatch(String resourceName) { - return resourceName.endsWith(".xml"); - } - })); - } + for (XmEbean mapping : mappings) { + List entityDeploy = mapping.getEntity(); + for (XmEntity deploy : entityDeploy) { + readEntityMapping(classLoader, deploy); } } - - long searchTime = System.currentTimeMillis() - st; - logger.debug("Classpath search mappings[{}] searchTime[{}]", resourceList.size(), searchTime); - return resourceList; } private void readEntityMapping(ClassLoader classLoader, XmEntity entityDeploy) { diff --git a/src/main/java/io/ebeaninternal/server/dto/DtoBeanDescriptor.java b/src/main/java/io/ebeaninternal/server/dto/DtoBeanDescriptor.java index 04060e8a4..0e8f4b42f 100644 --- a/src/main/java/io/ebeaninternal/server/dto/DtoBeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/dto/DtoBeanDescriptor.java @@ -1,7 +1,9 @@ package io.ebeaninternal.server.dto; import io.ebean.meta.MetricVisitor; - +import io.ebeaninternal.server.rawsql.SpiRawSql; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -10,15 +12,25 @@ import java.util.concurrent.ConcurrentHashMap; */ public class DtoBeanDescriptor { + private static final Map EMPTY_NAMED_QUERY = new HashMap<>(); + + private static final Map EMPTY_RAW_MAP = new HashMap<>(); + private final Map plans = new ConcurrentHashMap<>(); private final Class dtoType; private final DtoMeta meta; + private Map namedRawSql; + + private Map namedQuery; + DtoBeanDescriptor(Class dtoType, DtoMeta meta) { this.dtoType = dtoType; this.meta = meta; + this.namedQuery = getNamedQuery(); + this.namedRawSql = getNamedRawSql(); } public Class getType() { @@ -42,4 +54,52 @@ public class DtoBeanDescriptor { plan.visit(visitor); } } + + /** + * Return the named ORM query. + */ + public String getNamedQuery(String name) { + return namedQuery.get(name); + } + + /** + * Return the named RawSql query. + */ + public SpiRawSql getNamedRawSql(String named) { + return namedRawSql.get(named); + } + + /** + * Return the named ORM queries. + */ + public Map getNamedQuery() { + return (namedQuery != null) ? namedQuery : EMPTY_NAMED_QUERY; + } + + /** + * Add a named query. + */ + public void addNamedQuery(String name, String query) { + if (namedQuery == null) { + namedQuery = new LinkedHashMap<>(); + } + namedQuery.put(name, query); + } + + /** + * 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, SpiRawSql rawSql) { + if (namedRawSql == null) { + namedRawSql = new HashMap<>(); + } + namedRawSql.put(name, rawSql); + } } diff --git a/src/main/java/io/ebeaninternal/server/dto/DtoBeanManager.java b/src/main/java/io/ebeaninternal/server/dto/DtoBeanManager.java index 7cbd9488d..2871b6192 100644 --- a/src/main/java/io/ebeaninternal/server/dto/DtoBeanManager.java +++ b/src/main/java/io/ebeaninternal/server/dto/DtoBeanManager.java @@ -1,16 +1,28 @@ package io.ebeaninternal.server.dto; +import io.ebean.RawSqlBuilder; import io.ebean.meta.MetricVisitor; +import io.ebeaninternal.server.rawsql.SpiRawSql; import io.ebeaninternal.server.type.TypeManager; - +import io.ebeaninternal.xmlmapping.model.XmAliasMapping; +import io.ebeaninternal.xmlmapping.model.XmColumnMapping; +import io.ebeaninternal.xmlmapping.model.XmDto; +import io.ebeaninternal.xmlmapping.model.XmEbean; +import io.ebeaninternal.xmlmapping.model.XmNamedQuery; +import io.ebeaninternal.xmlmapping.model.XmRawSql; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Manages all the DTO bean descriptors. */ public class DtoBeanManager { + private static final Logger logger = LoggerFactory.getLogger(DtoBeanManager.class); + private final TypeManager typeManager; private final Map descriptorMap = new ConcurrentHashMap<>(); @@ -43,4 +55,45 @@ public class DtoBeanManager { value.visit(visitor); } } + + public void readXmlMapping(ClassLoader classLoader, List mappings) { + for (XmEbean mapping : mappings) { + List dtoList = mapping.getDto(); + for (XmDto dto : dtoList) { + readDtoMapping(classLoader, dto); + } + } + } + + private void readDtoMapping(ClassLoader classLoader, XmDto dto) { + String dtoClassName = dto.getClazz(); + Class dtoClass; + try { + dtoClass = Class.forName(dtoClassName, false, classLoader); + } catch (Exception e) { + logger.error("Could not load dto bean class " + dtoClassName + " for ebean xml entry"); + return; + } + + DtoBeanDescriptor dtoBeanDescriptor = getDescriptor(dtoClass); + if (dtoBeanDescriptor == null) { + logger.error("No dto bean for ebean xml entry " + dtoClass); + + } else { + for (XmRawSql sql : dto.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()); + } + dtoBeanDescriptor.addRawSql(sql.getName(), (SpiRawSql)builder.create()); + } + + for (XmNamedQuery namedQuery : dto.getNamedQuery()) { + dtoBeanDescriptor.addNamedQuery(namedQuery.getName(), namedQuery.getQuery().getValue()); + } + } + } } diff --git a/src/main/java/io/ebeaninternal/xmlmapping/XmlMappingReader.java b/src/main/java/io/ebeaninternal/xmlmapping/XmlMappingReader.java index e9e598e42..3e267349b 100644 --- a/src/main/java/io/ebeaninternal/xmlmapping/XmlMappingReader.java +++ b/src/main/java/io/ebeaninternal/xmlmapping/XmlMappingReader.java @@ -1,11 +1,17 @@ package io.ebeaninternal.xmlmapping; import io.ebeaninternal.xmlmapping.model.XmEbean; - +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; -import java.io.InputStream; +import org.avaje.classpath.scanner.Resource; public class XmlMappingReader { @@ -24,4 +30,34 @@ public class XmlMappingReader { throw new RuntimeException(e); } } + + public static List readByResourceName(ClassLoader classLoader, String resourceName){ + try { + Enumeration resources = classLoader.getResources(resourceName); + List mappings = new ArrayList<>(); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + try (InputStream is = url.openStream()) { + mappings.add(XmlMappingReader.read(is)); + } + } + return mappings; + } catch (IOException e) { + throw new RuntimeException("Error reading ebean xml mapping", e); + } + } + + public static List readByResourceList(ClassLoader classLoader, List resourceList){ + try { + List mappings = new ArrayList<>(); + for (Resource xmlMappingRes : resourceList) { + try (InputStream is = new FileInputStream(xmlMappingRes.getLocationOnDisk())) { + mappings.add(XmlMappingReader.read(is)); + } + } + return mappings; + } catch (IOException e) { + throw new RuntimeException("Error reading ebean xml mapping", e); + } + } } diff --git a/src/main/java/io/ebeaninternal/xmlmapping/model/XmDto.java b/src/main/java/io/ebeaninternal/xmlmapping/model/XmDto.java new file mode 100644 index 000000000..cb64fbfa6 --- /dev/null +++ b/src/main/java/io/ebeaninternal/xmlmapping/model/XmDto.java @@ -0,0 +1,121 @@ +package io.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}named-query" maxOccurs="unbounded"/>
+ *         <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 = { + "namedQuery", + "rawSql" +}) +@XmlRootElement(name = "dto") +public class XmDto { + + @XmlElement(name = "named-query", required = true) + protected List namedQuery; + @XmlElement(name = "raw-sql", required = true) + protected List rawSql; + @XmlAttribute(name = "class", required = true) + protected String clazz; + + /** + * Gets the value of the namedQuery 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 namedQuery property. + *

+ *

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

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

+ *

+ *

+ * Objects of the following type(s) are allowed in the list + * {@link XmNamedQuery } + */ + public List getNamedQuery() { + if (namedQuery == null) { + namedQuery = new ArrayList<>(); + } + return this.namedQuery; + } + + /** + * 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/io/ebeaninternal/xmlmapping/model/XmEbean.java b/src/main/java/io/ebeaninternal/xmlmapping/model/XmEbean.java index d1bc12236..4979fc865 100644 --- a/src/main/java/io/ebeaninternal/xmlmapping/model/XmEbean.java +++ b/src/main/java/io/ebeaninternal/xmlmapping/model/XmEbean.java @@ -1,12 +1,12 @@ package io.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; -import java.util.ArrayList; -import java.util.List; /** @@ -28,14 +28,18 @@ import java.util.List; */ @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "", propOrder = { - "entity" + "entity", + "dto" }) @XmlRootElement(name = "ebean") public class XmEbean { - @XmlElement(required = true) + @XmlElement(required = false) protected List entity; + @XmlElement(required = false) + protected List dto; + /** * Gets the value of the entity property. *

@@ -63,4 +67,11 @@ public class XmEbean { return this.entity; } + public List getDto() { + if (dto == null) { + dto = new ArrayList<>(); + } + return this.dto; + } + } diff --git a/src/main/resources/ebean-1.0.xsd b/src/main/resources/ebean-1.0.xsd index c830f851f..4a79daecb 100644 --- a/src/main/resources/ebean-1.0.xsd +++ b/src/main/resources/ebean-1.0.xsd @@ -8,6 +8,7 @@ + @@ -22,6 +23,16 @@ + + + + + + + + + + diff --git a/src/test/java/io/ebean/NamedDtoQueryTest.java b/src/test/java/io/ebean/NamedDtoQueryTest.java new file mode 100644 index 000000000..ec3b810e4 --- /dev/null +++ b/src/test/java/io/ebean/NamedDtoQueryTest.java @@ -0,0 +1,335 @@ +package io.ebean; + +import io.ebean.meta.BasicMetricVisitor; +import io.ebean.meta.MetaQueryMetric; +import java.util.List; +import java.util.Optional; +import org.ebeantest.LoggedSqlCollector; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.tests.model.basic.ResetBasicData; + +import static org.assertj.core.api.Assertions.assertThat; + +public class NamedDtoQueryTest extends BaseTestCase { + + private static final Logger log = LoggerFactory.getLogger(NamedDtoQueryTest.class); + + @Test + public void dto_findList_constructorMatch() { + + ResetBasicData.reset(); + + DtoQuery dtoQuery = server().createNamedDtoQuery(DCust.class, "findList"); + + List list = dtoQuery.findList(); + + log.info(list.toString()); + assertThat(list).isNotEmpty(); + } + + @Test + public void dto_findEach_constructorMatch() { + + ResetBasicData.reset(); + + LoggedSqlCollector.start(); + server().createNamedDtoQuery(DCust.class, "findGtId") + .setParameter("id", 0) + .findEach(it -> log.info("got " + it.getId() + " " + it.getName())); + + List sql = LoggedSqlCollector.stop(); + assertThat(sql.get(0)).contains("select id, name from o_customer where id > ?"); + } + + @Test + public void dto_findEachWhile_constructorMatch() { + + ResetBasicData.reset(); + + LoggedSqlCollector.start(); + server().createNamedDtoQuery(DCust.class, "findGtIdDesc") + .setParameter("id", 0) + .findEachWhile(customer -> { + log.info("got " + customer.getId() + " " + customer.getName()); + return customer.getId() > 3; + }); + + List sql = LoggedSqlCollector.stop(); + assertThat(sql.get(0)).contains("select id, name from o_customer where id > ?"); + } + + @Test + public void dto_findOneEmpty() { + + ResetBasicData.reset(); + + Optional rob = server().createNamedDtoQuery(DCust.class, "findByName") + .setParameter("name", "Fiona") + .findOneOrEmpty(); + + assertThat(rob.isPresent()).isTrue(); + + Optional oneOrEmpty = server().createNamedDtoQuery(DCust.class, "findByName") + .setParameter("name", "DoesNotExistMyFriend") + .findOneOrEmpty(); + + assertThat(oneOrEmpty.isPresent()).isFalse(); + } + + @Test + public void dto_findOne() { + + ResetBasicData.reset(); + + DCust fiona = server().createNamedDtoQuery(DCust.class, "findByName") + .setParameter("name", "Fiona") + .findOne(); + + assertThat(fiona.getName()).isEqualTo("Fiona"); + + DCust empty = server().createNamedDtoQuery(DCust.class, "findByName") + .setParameter("name", "DoesNotExistMyFriend") + .findOne(); + + assertThat(empty).isNull(); + } + + + @Test + public void dto_queryPlanHits() { + + ResetBasicData.reset(); + + resetAllMetrics(); + + String[] names = {"Rob", "Fiona", "Shrek"}; + + for (String name : names) { + + List custs = server().createNamedDtoQuery(DCust.class, "findByName_c3") + .setLabel("basic") + .setParameter("name", name) + .findList(); + + log.info("Found " + custs); + } + + // collect without reset + BasicMetricVisitor basic = new BasicMetricVisitor(false, true, true); + server().getMetaInfoManager().visitMetrics(basic); + + List stats = basic.getDtoQueryMetrics(); + assertThat(stats).hasSize(1); + + MetaQueryMetric queryMetric = stats.get(0); + assertThat(queryMetric.getLabel()).isEqualTo("basic"); + assertThat(queryMetric.getCount()).isEqualTo(3); + assertThat(queryMetric.getName()).isEqualTo("basic"); + + + server().createNamedDtoQuery(DCust.class, "findByName_c4") + .setLabel("basic2") + .setParameter("name", "rob") + .findList(); + + BasicMetricVisitor metric2 = server().getMetaInfoManager().visitBasic(); + + stats = metric2.getDtoQueryMetrics(); + assertThat(stats).hasSize(2); + + log.info("stats " + stats); + + } + + @Test + public void dto_findList_relaxedMode() { + + ResetBasicData.reset(); + + List list = server().createNamedDtoQuery(DCust3.class, "findByName_relaxedMode") + .setRelaxedMode() + .findList(); + + log.info(list.toString()); + assertThat(list).isNotEmpty(); + } + + @Test + public void dto_findList_relaxedMode_defaultConstructor() { + + ResetBasicData.reset(); + + List list = server().createNamedDtoQuery(DCust2.class, "findByName_relaxedMode") + .setRelaxedMode() + .findList(); + + log.info(list.toString()); + assertThat(list).isNotEmpty(); + } + + @Test + public void dto_findList_constructorPlusMatch() { + + ResetBasicData.reset(); + + List dtos = server().createNamedDtoQuery(DCust.class, "findListByName") + .setParameter("name", "Rob") + .findList(); + + log.info(dtos.toString()); + assertThat(dtos).isNotEmpty(); + } + + @Test + public void dto_findList_setters() { + + ResetBasicData.reset(); + + DtoQuery dtoQuery = server().createNamedDtoQuery(DCust2.class, "findList"); + + List list = dtoQuery.findList(); + + assertThat(list).isNotEmpty(); + } + + @Test + public void dto3_findList_constructorMatch() { + + ResetBasicData.reset(); + + List robs = server().createNamedDtoQuery(DCust3.class, "findByNameLike") + .setParameter(1, "Rob") + .setMaxRows(10) + .findList(); + + + log.info(robs.toString()); + assertThat(robs).isNotEmpty(); + } + + @Test + public void dto3_findList_settersMatch() { + + ResetBasicData.reset(); + + List robs = server().createNamedDtoQuery(DCust3.class, "findByName") + .setParameter("name", "Rob") + .findList(); + + + log.info(robs.toString()); + assertThat(robs).isNotEmpty(); + } + + public static class DCust { + + final Integer id; + + final String name; + + int totalOrders; + + public DCust(Integer id, String name) { + this.id = id; + this.name = name; + } + + @Override + public String toString() { + return "id:" + id + " name:" + name + " totalOrders:" + totalOrders; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public int getTotalOrders() { + return totalOrders; + } + + public void setTotalOrders(int totalOrders) { + this.totalOrders = totalOrders; + } + } + + public static class DCust2 { + + Integer id; + + String name; + + @Override + public String toString() { + return "id:" + id + " name:" + name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public static class DCust3 { + + Integer id; + + String name; + + int totalOrders; + + public DCust3() { + } + + public DCust3(Integer id, String name, int totalOrders) { + this.id = id; + this.name = name; + this.totalOrders = totalOrders; + } + + @Override + public String toString() { + return "id:" + id + " name:" + name + " totalOrders:" + totalOrders; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public int getTotalOrders() { + return totalOrders; + } + + public void setTotalOrders(int totalOrders) { + this.totalOrders = totalOrders; + } + + public void setId(Integer id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java index 5755f6ed5..5f538f9d5 100644 --- a/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java +++ b/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java @@ -48,9 +48,6 @@ import io.ebeaninternal.server.core.timezone.DataTimeZone; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.query.CQuery; import io.ebeaninternal.server.transaction.RemoteTransactionEvent; - -import javax.persistence.OptimisticLockException; -import javax.persistence.PersistenceException; import java.lang.reflect.Type; import java.util.Collection; import java.util.Collections; @@ -61,6 +58,8 @@ import java.util.Set; import java.util.concurrent.Callable; import java.util.function.Consumer; import java.util.function.Predicate; +import javax.persistence.OptimisticLockException; +import javax.persistence.PersistenceException; /** @@ -432,6 +431,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer { return null; } + @Override + public DtoQuery createNamedDtoQuery(Class dtoType, String namedQuery) { + return null; + } + @Override public DtoQuery findDto(Class dtoType, SpiQuery ormQuery) { return null; diff --git a/src/test/resources/ebean-xml-mappings/DCustMapping.xml b/src/test/resources/ebean-xml-mappings/DCustMapping.xml new file mode 100644 index 000000000..c104f4cd5 --- /dev/null +++ b/src/test/resources/ebean-xml-mappings/DCustMapping.xml @@ -0,0 +1,77 @@ + + + + + + + select id, name from o_customer + + + + + select id, name from o_customer where id > :id + + + + + select id, name from o_customer where id > :id order by id desc + + + + + select id, name from o_customer where name = :name + + + + + select c3.id, c3.name from o_customer c3 where c3.name = :name + + + + + select c4.id, c4.name from o_customer c4 where lower(c4.name) = :name + + + + + select c.id, c.name, count(o.id) as totalOrders + from o_customer c + join o_order o on o.kcustomer_id = c.id + where c.name like :name + group by c.id, c.name + + + + + + + + select id, '42' as something_we_cannot_map, name from o_customer + + + + + select id, name from o_customer + + + + + + + + select id, name, 42 as total, '42' as something_we_cannot_map from o_customer + + + + + select id, name, 42 as totalOrders from o_customer where name like ? + + + + + select id, name from o_customer where name = :name + + + + +