#765 - ENH: Add ebean.xml with support for named RawSql, also adds EbeanServer.createNamedQuery()

This commit is contained in:
Robin Bygrave
2016-07-08 22:35:04 +12:00
parent 2c3c23725a
commit c7c6410cd9
30 changed files with 1116 additions and 132 deletions
+15
View File
@@ -945,6 +945,21 @@ public final class Ebean {
return serverMgr.getDefaultServer().createCsvReader(beanType);
}
/**
* Create a named query.
* <p>
* For RawSql the named query is expected to be in ebean.xml.
* </p>
*
* @param beanType The type of entity bean
* @param namedQuery The name of the query
* @param <T> The type of entity bean
* @return The query
*/
public static <T> Query<T> createNamedQuery(Class<T> beanType, String namedQuery) {
return serverMgr.getDefaultServer().createNamedQuery(beanType, namedQuery);
}
/**
* Create a query for a type of entity bean.
* <p>
@@ -199,6 +199,19 @@ public interface EbeanServer {
*/
<T> UpdateQuery<T> update(Class<T> beanType);
/**
* Create a named query.
* <p>
* For RawSql the named query is expected to be in ebean.xml.
* </p>
*
* @param beanType The type of entity bean
* @param namedQuery The name of the query
* @param <T> The type of entity bean
* @return The query
*/
<T> Query<T> createNamedQuery(Class<T> beanType, String namedQuery);
/**
* Create a query for an entity bean and synonym for {@link #find(Class)}.
*
@@ -564,6 +564,11 @@ public interface SpiQuery<T> extends Query<T> {
*/
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.
*/
@@ -887,6 +887,21 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return createQuery(beanType);
}
@Override
public <T> Query<T> createNamedQuery(Class<T> beanType, String namedQuery) {
BeanDescriptor<T> 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<T> query = createQuery(beanType);
query.setRawSql(rawSql);
return query;
}
throw new PersistenceException("No named query called " + namedQuery + " for bean:" + beanType.getName());
}
public <T> DefaultOrmQuery<T> createQuery(Class<T> beanType) {
BeanDescriptor<T> desc = getBeanDescriptor(beanType);
if (desc == null) {
@@ -945,6 +960,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private <T> SpiOrmQueryRequest<T> createQueryRequest(SpiQuery<T> query, Transaction t) {
query.setDefaultRawSqlIfRequired();
if (query.isAutoTunable() && !autoTuneService.tuneQuery(query)) {
// use deployment FetchType.LAZY/EAGER annotations
// to define the 'default' select clause
@@ -109,6 +109,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
private final ConcurrentHashMap<String, ElComparator<T>> comparatorCache = new ConcurrentHashMap<String, ElComparator<T>>();
private final Map<String, RawSql> namedRawSql;
public void merge(EntityBean bean, EntityBean existing) {
EntityBeanIntercept fromEbi = bean._ebean_getIntercept();
@@ -412,6 +414,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
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<T> implements MetaBeanInfo, BeanType<T> {
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<T> implements MetaBeanInfo, BeanType<T> {
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<T> implements MetaBeanInfo, BeanType<T> {
* 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<T> implements MetaBeanInfo, BeanType<T> {
}
/**
* 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.
* <p>
* 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).
* </p>
* Returns true if this bean is based on RawSql.
*/
public boolean isSqlSelectBased() {
return EntityType.SQL.equals(entityType);
public boolean isRawSqlBased() {
return EntityType.SQL == entityType;
}
/**
@@ -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<Class<?>, DeployBeanInfo<?>> deplyInfoMap = new HashMap<Class<?>, DeployBeanInfo<?>>();
private Map<Class<?>, DeployBeanInfo<?>> deployInfoMap = new HashMap<Class<?>, DeployBeanInfo<?>>();
private final Map<Class<?>, BeanTable> beanTableMap = new HashMap<Class<?>, 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<URL> resources = classLoader.getResources("ebean.xml");
List<XmEbean> mappings = new ArrayList<XmEbean>();
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
InputStream is = url.openStream();
mappings.add(XmlMappingReader.read(is));
is.close();
}
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", 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 <T> DeployBeanInfo<T> getDeploy(Class<T> cls) {
return (DeployBeanInfo<T>) deplyInfoMap.get(cls);
return (DeployBeanInfo<T>) 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);
@@ -322,7 +322,7 @@ public abstract class BeanPropertyAssoc<T> 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);
}
@@ -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<T> {
private static final Map<String, RawSql> EMPTY_RAW_MAP = new HashMap<String, RawSql>();
private static class PropOrder implements Comparator<DeployBeanProperty> {
public int compare(DeployBeanProperty o1, DeployBeanProperty o2) {
@@ -66,6 +71,8 @@ public class DeployBeanDescriptor<T> {
*/
private LinkedHashMap<String, DeployBeanProperty> propMap = new LinkedHashMap<String, DeployBeanProperty>();
private Map<String, RawSql> namedRawSql;
private EntityType entityType;
private DeployBeanPropertyAssocOne<?> unidirectional;
@@ -1047,4 +1054,21 @@ public class DeployBeanDescriptor<T> {
if (docStorePersist != DocStoreMode.DEFAULT) return docStorePersist;
return serverConfig.getDocStoreConfig().getPersist();
}
/**
* Return the named RawSql queries.
*/
public Map<String, RawSql> 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<String, RawSql>();
}
namedRawSql.put(name, rawSql);
}
}
@@ -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<T> {
tableJoin.setType(outerJoin ? SqlJoinType.OUTER : SqlJoinType.INNER);
}
/**
* Add named RawSql from ebean.xml.
*/
public void addRawSql(String name, RawSql rawSql) {
descriptor.addRawSql(name, rawSql);
}
}
@@ -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();
@@ -38,6 +38,8 @@ import java.util.Set;
*/
public class DefaultOrmQuery<T> implements SpiQuery<T> {
public static final String DEFAULT_QUERY_NAME = "default";
private final Class<T> beanType;
private final BeanDescriptor<T> beanDescriptor;
@@ -699,6 +701,13 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return forUpdate;
}
@Override
public void setDefaultRawSqlIfRequired() {
if (beanDescriptor.isRawSqlBased() && rawSql == null) {
rawSql = beanDescriptor.getNamedRawSql(DEFAULT_QUERY_NAME);
}
}
@Override
public DefaultOrmQuery<T> setAutoTune(boolean autoTune) {
this.autoTune = autoTune;
@@ -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);
}
}
}
@@ -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.
* <p>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();
}
}
@@ -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;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;attribute name="alias" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="property" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@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;
}
}
@@ -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;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;attribute name="column" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="property" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@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;
}
}
@@ -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;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <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}entity" maxOccurs="unbounded"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"entity"
})
@XmlRootElement(name = "ebean")
public class XmEbean {
@XmlElement(required = true)
protected List<XmEntity> entity;
/**
* Gets the value of the entity property.
*
* <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 entity property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getEntity().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link XmEntity }
*
*
*/
public List<XmEntity> getEntity() {
if (entity == null) {
entity = new ArrayList<XmEntity>();
}
return this.entity;
}
}
@@ -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;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <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}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 = {
"rawSql"
})
@XmlRootElement(name = "entity")
public class XmEntity {
@XmlElement(name = "raw-sql", required = true)
protected List<XmRawSql> rawSql;
@XmlAttribute(name = "class", required = true)
protected String clazz;
/**
* Gets the value of the rawSql property.
*
* <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>
* For example, to add a new item, do as follows:
* <pre>
* getRawSql().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link XmRawSql }
*
*
*/
public List<XmRawSql> getRawSql() {
if (rawSql == null) {
rawSql = new ArrayList<XmRawSql>();
}
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;
}
}
@@ -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;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;simpleContent>
* &lt;extension base="&lt;http://www.w3.org/2001/XMLSchema>string">
* &lt;/extension>
* &lt;/simpleContent>
* &lt;/complexType>
* </pre>
*
*
*/
@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;
}
}
@@ -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;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <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}alias-mapping" maxOccurs="unbounded" minOccurs="0"/>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/ebean}column-mapping" maxOccurs="unbounded" minOccurs="0"/>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/ebean}query"/>
* &lt;/sequence>
* &lt;attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"aliasMapping",
"columnMapping",
"query"
})
@XmlRootElement(name = "raw-sql")
public class XmRawSql {
@XmlElement(name = "alias-mapping")
protected List<XmAliasMapping> aliasMapping;
@XmlElement(name = "column-mapping")
protected List<XmColumnMapping> columnMapping;
@XmlElement(required = true)
protected XmQuery query;
@XmlAttribute(name = "name", required = true)
protected String name;
/**
* Gets the value of the aliasMapping property.
*
* <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 aliasMapping property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getAliasMapping().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link XmAliasMapping }
*
*
*/
public List<XmAliasMapping> getAliasMapping() {
if (aliasMapping == null) {
aliasMapping = new ArrayList<XmAliasMapping>();
}
return this.aliasMapping;
}
/**
* Gets the value of the columnMapping property.
*
* <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 columnMapping property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getColumnMapping().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link XmColumnMapping }
*
*
*/
public List<XmColumnMapping> getColumnMapping() {
if (columnMapping == null) {
columnMapping = new ArrayList<XmColumnMapping>();
}
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;
}
}
@@ -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;
+59
View File
@@ -0,0 +1,59 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://ebean-orm.github.io/xml/ns/ebean"
targetNamespace="http://ebean-orm.github.io/xml/ns/ebean" elementFormDefault="qualified">
<!-- Root level type : extra-ddl -->
<xsd:element name="ebean">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="entity" minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="entity">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="raw-sql" minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="class" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="raw-sql">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="alias-mapping" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="column-mapping" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="query" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="alias-mapping">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" use="required"/>
<xsd:attribute name="property" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="column-mapping">
<xsd:complexType>
<xsd:attribute name="column" type="xsd:string" use="required"/>
<xsd:attribute name="property" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="query">
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<!--<xsd:attribute name="class" type="xsd:string" use="required"/>-->
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>
@@ -285,6 +285,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
return null;
}
@Override
public <T> Query<T> createNamedQuery(Class<T> beanType, String namedQuery) {
return null;
}
@Override
public <T> Query<T> createQuery(Class<T> beanType) {
return null;
@@ -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);
}
}
@@ -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<OrderAggregate> query = Ebean.find(OrderAggregate.class);
List<OrderAggregate> 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<OrderAggregate> query = Ebean.getDefaultServer().createNamedQuery(OrderAggregate.class, "withMax");
List<OrderAggregate> 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<OrderAggregate> query = Ebean.getDefaultServer().createNamedQuery(OrderAggregate.class, "withMax");
List<OrderAggregate> 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<OrderAggregate> query = Ebean.getDefaultServer().createNamedQuery(OrderAggregate.class, "withParam");
List<OrderAggregate> 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);
}
}
@@ -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;
}
}
@@ -22,6 +22,25 @@ import static org.assertj.core.api.Assertions.assertThat;
public class TestRawSqlOrmQuery extends BaseTestCase {
@Test
public void testNamed() {
ResetBasicData.reset();
Query<Order> query = Ebean.createNamedQuery(Order.class, "myRawTest");
query.setParameter("orderStatus", Order.Status.NEW);
query.setMaxRows(10);
List<Order> 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() {
-57
View File
@@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings>
<entity class="com.avaje.tests.model.basic.OrderAggregate">
<raw-sql name="default">
<columnMapping column="order_id" property="order.id" />
<query>
select order_id, count(*) as totalItems, sum(order_qty*unit_price) as totalAmount
from o_order_detail
group by order_id
</query>
</raw-sql>
<raw-sql name="total.amount">
<columnMapping column="order_id" property="order.id" />
<query>
select order_id, sum(order_qty*unit_price) as totalAmount
from o_order_detail
group by order_id
</query>
</raw-sql>
<sql-select name="total.qty">
<query>
select order_id, count(*) as total_items, sum(order_qty*unit_price) as total_amount
from o_order_detail
group by order_id
</query>
</sql-select>
<sql-select name="total">
<query>
select order_id, sum(order_qty*unit_price) as total_amount
from o_order_detail
group by order_id
</query>
</sql-select>
</entity>
<entity class="com.avaje.tests.model.basic.TMapSuperEntity">
<raw-sql name="testTransient">
<columnMapping column="id" property="id" />
<columnMapping column="name" property="name" />
<query>
select id, name, 12 as myint from t_mapsuper1
</query>
</raw-sql>
</entity>
<entity class="com.avaje.tests.model.basic.MyAdHoc">
<raw-sql name="default">
<columnMapping column="order_id" property="order.id" />
<query>
select order_id, count(*) as detailCount from o_order_detail group by order_id
</query>
</raw-sql>
</entity>
</entity-mappings>
+51
View File
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ebean xmlns="http://ebean-orm.github.io/xml/ns/ebean">
<entity class="com.avaje.tests.model.basic.OrderAggregate">
<raw-sql name="default">
<column-mapping column="order_id" property="order.id"/>
<column-mapping column="total_items" property="totalItems"/>
<column-mapping column="total_amount" property="totalAmount"/>
<query>
select order_id, count(*) as total_items, sum(order_qty*unit_price) as total_amount
from o_order_detail
group by order_id
</query>
</raw-sql>
<raw-sql name="withMax">
<column-mapping column="order_id" property="order.id"/>
<query>
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
</query>
</raw-sql>
<raw-sql name="withParam">
<column-mapping column="order_id" property="order.id"/>
<query>
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
</query>
</raw-sql>
</entity>
<entity class="com.avaje.tests.model.basic.Order">
<raw-sql name="myRawTest">
<alias-mapping alias="c" property="customer"/>
<alias-mapping alias="a" property="customer.billingAddress"/>
<query>
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
</query>
</raw-sql>
</entity>
</ebean>
-40
View File
@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="1.0" xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_1_0.xsd">
<package>com.avaje.tests.model.basic</package>
<entity class="com.avaje.tests.model.basic.Order">
<named-query name="top.open">
<query>
find order
join customer (name)
join customer.billingAddress (*)
join details
join details.product (sku, name)
order by customer.name asc
limit 10
</query>
</named-query>
<named-query name="top.open">
<query>
find order
join customer (name)
join customer.billingAddress (*)
join details
join details.product (sku, name)
order by customer.name asc
limit 10
</query>
</named-query>
</entity>
<named-native-query name="find.order">
<query>
select * from o_order where id = :id
</query>
</named-native-query>
</entity-mappings>
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ebean xmlns="http://ebean-orm.github.io/xml/ns/ebean">
<entity class="com.avaje.tests.model.basic.OrderAggregate">
<raw-sql name="default">
<column-mapping column="order_id" property="order.id"/>
<query>
select order_id, count(*) as totalItems, sum(order_qty*unit_price) as totalAmount
from o_order_detail
group by order_id
</query>
</raw-sql>
<raw-sql name="default">
<column-mapping column="order_id" property="order.id"/>
<query>
select order_id, count(*) as totalItems, sum(order_qty*unit_price) as totalAmount
from o_order_detail
group by order_id
</query>
</raw-sql>
</entity>
</ebean>