ENH: Add named DtoQuery, such that we can define native SQL queries in ebean mapping xml for DtoQuery.

This commit is contained in:
yuanxuegui
2018-03-19 18:04:49 +08:00
parent 9d336cb20b
commit 5575273c96
13 changed files with 814 additions and 104 deletions
+19 -6
View File
@@ -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 {
*/
<T> DtoQuery<T> findDto(Class<T> dtoType, String sql);
/**
* Create a named Query for DTO beans.
* <p>
* 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.
* </p>
*
* @param dtoType The type of the DTO bean the rows will be mapped into.
* @param namedQuery The name of the query
* @param <T> The type of the DTO bean.
*/
<T> DtoQuery<T> createNamedDtoQuery(Class<T> dtoType, String namedQuery);
/**
* Create a SqlQuery for executing native sql
* query statements.
@@ -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 <T> Query<T> createQuery(Class<T> beanType, String eql) {
public <T> DefaultOrmQuery<T> createQuery(Class<T> beanType, String eql) {
DefaultOrmQuery<T> 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 <T> DtoQuery<T> createNamedDtoQuery(Class<T> dtoType, String namedQuery) {
DtoBeanDescriptor<T> 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 <T> DtoQuery<T> findDto(Class<T> dtoType, SpiQuery<?> ormQuery) {
@@ -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<XmEbean> 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<Resource> 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<String, String> asOfTableMapping = beanDescriptorManager.deploy();
Map<String, String> asOfTableMapping = beanDescriptorManager.deploy(xmlEbeanList);
Map<String, String> draftTableMap = beanDescriptorManager.getDraftTableMap();
beanDescriptorManager.scheduleBackgroundTrim();
@@ -167,6 +181,31 @@ public class InternalConfiguration {
this.cQueryEngine = new CQueryEngine(serverConfig, databasePlatform, binder, asOfTableMapping, draftTableMap);
}
private List<Resource> searchXmlMapping() {
List<ClassPathScanner> scanners = ClassPathScanners.find(serverConfig);
List<String> mappingLocations = serverConfig.getMappingLocations();
List<Resource> 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.
*/
@@ -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<String, String> deploy() {
public Map<String, String> deploy(List<XmEbean> 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<XmEbean> mappings) {
ClassLoader classLoader = serverConfig.getClassLoadConfig().getClassLoader();
try {
ClassLoader classLoader = serverConfig.getClassLoadConfig().getClassLoader();
Enumeration<URL> resources = classLoader.getResources("ebean.xml");
List<XmEbean> mappings = new ArrayList<>();
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
try (InputStream is = url.openStream()) {
mappings.add(XmlMappingReader.read(is));
}
}
List<Resource> xmlMappingResources = searchXmlMapping();
for (Resource xmlMappingRes : xmlMappingResources) {
try (InputStream is = new FileInputStream(xmlMappingRes.getLocationOnDisk())) {
mappings.add(XmlMappingReader.read(is));
}
}
for (XmEbean mapping : mappings) {
List<XmEntity> entityDeploy = mapping.getEntity();
for (XmEntity deploy : entityDeploy) {
readEntityMapping(classLoader, deploy);
}
}
} catch (IOException e) {
throw new RuntimeException("Error reading ebean xml mapping", e);
}
}
private List<Resource> searchXmlMapping() {
List<ClassPathScanner> scanners = ClassPathScanners.find(serverConfig);
List<String> mappingLocations = serverConfig.getMappingLocations();
List<Resource> 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<XmEntity> 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) {
@@ -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<T> {
private static final Map<String, String> EMPTY_NAMED_QUERY = new HashMap<>();
private static final Map<String, SpiRawSql> EMPTY_RAW_MAP = new HashMap<>();
private final Map<Object, DtoQueryPlan> plans = new ConcurrentHashMap<>();
private final Class<T> dtoType;
private final DtoMeta meta;
private Map<String, SpiRawSql> namedRawSql;
private Map<String, String> namedQuery;
DtoBeanDescriptor(Class<T> dtoType, DtoMeta meta) {
this.dtoType = dtoType;
this.meta = meta;
this.namedQuery = getNamedQuery();
this.namedRawSql = getNamedRawSql();
}
public Class<T> getType() {
@@ -42,4 +54,52 @@ public class DtoBeanDescriptor<T> {
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<String, String> 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<String, SpiRawSql> 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);
}
}
@@ -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<Class, DtoBeanDescriptor> descriptorMap = new ConcurrentHashMap<>();
@@ -43,4 +55,45 @@ public class DtoBeanManager {
value.visit(visitor);
}
}
public void readXmlMapping(ClassLoader classLoader, List<XmEbean> mappings) {
for (XmEbean mapping : mappings) {
List<XmDto> 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());
}
}
}
}
@@ -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<XmEbean> readByResourceName(ClassLoader classLoader, String resourceName){
try {
Enumeration<URL> resources = classLoader.getResources(resourceName);
List<XmEbean> 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<XmEbean> readByResourceList(ClassLoader classLoader, List<Resource> resourceList){
try {
List<XmEbean> 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);
}
}
}
@@ -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;
/**
* <p>Java class for anonymous complex type.
* <p>
* <p>The following schema fragment specifies the expected content contained within this class.
* <p>
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/ebean}named-query" maxOccurs="unbounded"/>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/ebean}raw-sql" maxOccurs="unbounded"/>
* &lt;/sequence>
* &lt;attribute name="class" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"namedQuery",
"rawSql"
})
@XmlRootElement(name = "dto")
public class XmDto {
@XmlElement(name = "named-query", required = true)
protected List<XmNamedQuery> namedQuery;
@XmlElement(name = "raw-sql", required = true)
protected List<XmRawSql> rawSql;
@XmlAttribute(name = "class", required = true)
protected String clazz;
/**
* Gets the value of the namedQuery property.
* <p>
* <p>
* 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 <CODE>set</CODE> method for the namedQuery property.
* <p>
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getNamedQuery().add(newItem);
* </pre>
* <p>
* <p>
* <p>
* Objects of the following type(s) are allowed in the list
* {@link XmNamedQuery }
*/
public List<XmNamedQuery> getNamedQuery() {
if (namedQuery == null) {
namedQuery = new ArrayList<>();
}
return this.namedQuery;
}
/**
* Gets the value of the rawSql property.
* <p>
* <p>
* 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 <CODE>set</CODE> method for the rawSql property.
* <p>
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getRawSql().add(newItem);
* </pre>
* <p>
* <p>
* <p>
* Objects of the following type(s) are allowed in the list
* {@link XmRawSql }
*/
public List<XmRawSql> 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;
}
}
@@ -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<XmEntity> entity;
@XmlElement(required = false)
protected List<XmDto> dto;
/**
* Gets the value of the entity property.
* <p>
@@ -63,4 +67,11 @@ public class XmEbean {
return this.entity;
}
public List<XmDto> getDto() {
if (dto == null) {
dto = new ArrayList<>();
}
return this.dto;
}
}
+11
View File
@@ -8,6 +8,7 @@
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="entity" minOccurs="1" maxOccurs="unbounded"/>
<xsd:element ref="dto" minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
@@ -22,6 +23,16 @@
</xsd:complexType>
</xsd:element>
<xsd:element name="dto">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="named-query" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="raw-sql" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="class" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="named-query">
<xsd:complexType>
<xsd:sequence>
@@ -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<DCust> dtoQuery = server().createNamedDtoQuery(DCust.class, "findList");
List<DCust> 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<String> 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<String> sql = LoggedSqlCollector.stop();
assertThat(sql.get(0)).contains("select id, name from o_customer where id > ?");
}
@Test
public void dto_findOneEmpty() {
ResetBasicData.reset();
Optional<DCust> rob = server().createNamedDtoQuery(DCust.class, "findByName")
.setParameter("name", "Fiona")
.findOneOrEmpty();
assertThat(rob.isPresent()).isTrue();
Optional<DCust> 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<DCust> 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<MetaQueryMetric> 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<DCust3> 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<DCust2> 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<DCust> 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<DCust2> dtoQuery = server().createNamedDtoQuery(DCust2.class, "findList");
List<DCust2> list = dtoQuery.findList();
assertThat(list).isNotEmpty();
}
@Test
public void dto3_findList_constructorMatch() {
ResetBasicData.reset();
List<DCust3> 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<DCust3> 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;
}
}
}
@@ -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 <T> DtoQuery<T> createNamedDtoQuery(Class<T> dtoType, String namedQuery) {
return null;
}
@Override
public <D> DtoQuery<D> findDto(Class<D> dtoType, SpiQuery<?> ormQuery) {
return null;
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ebean xmlns="http://ebean-orm.github.io/xml/ns/ebean">
<dto class="io.ebean.NamedDtoQueryTest$DCust">
<raw-sql name="findList">
<query>
select id, name from o_customer
</query>
</raw-sql>
<raw-sql name="findGtId">
<query>
select id, name from o_customer where id > :id
</query>
</raw-sql>
<raw-sql name="findGtIdDesc">
<query>
select id, name from o_customer where id > :id order by id desc
</query>
</raw-sql>
<raw-sql name="findByName">
<query>
select id, name from o_customer where name = :name
</query>
</raw-sql>
<raw-sql name="findByName_c3">
<query>
select c3.id, c3.name from o_customer c3 where c3.name = :name
</query>
</raw-sql>
<raw-sql name="findByName_c4">
<query>
select c4.id, c4.name from o_customer c4 where lower(c4.name) = :name
</query>
</raw-sql>
<raw-sql name="findListByName">
<query>
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
</query>
</raw-sql>
</dto>
<dto class="io.ebean.NamedDtoQueryTest$DCust2">
<raw-sql name="findByName_relaxedMode">
<query>
select id, '42' as something_we_cannot_map, name from o_customer
</query>
</raw-sql>
<raw-sql name="findList">
<query>
select id, name from o_customer
</query>
</raw-sql>
</dto>
<dto class="io.ebean.NamedDtoQueryTest$DCust3">
<raw-sql name="findByName_relaxedMode">
<query>
select id, name, 42 as total, '42' as something_we_cannot_map from o_customer
</query>
</raw-sql>
<raw-sql name="findByNameLike">
<query>
select id, name, 42 as totalOrders from o_customer where name like ?
</query>
</raw-sql>
<raw-sql name="findByName">
<query>
select id, name from o_customer where name = :name
</query>
</raw-sql>
</dto>
</ebean>