Java tutorial
/* * Hibernate, Relational Persistence for Idiomatic Java * * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.criterion; import java.io.Serializable; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; import org.hibernate.Criteria; import org.hibernate.EntityMode; import org.hibernate.engine.spi.TypedValue; import org.hibernate.internal.util.StringHelper; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.type.CompositeType; import org.hibernate.type.Type; /** * Support for query by example. * * <pre> * List results = session.createCriteria(Parent.class) * .add( Example.create(parent).ignoreCase() ) * .createCriteria("child") * .add( Example.create( parent.getChild() ) ) * .list(); * </pre> * * "Examples" may be mixed and matched with "Expressions" in the same Criteria. * * @see org.hibernate.Criteria * @author Gavin King */ public class Example implements Criterion { private final Object exampleEntity; private PropertySelector selector; private boolean isLikeEnabled; private Character escapeCharacter; private boolean isIgnoreCaseEnabled; private MatchMode matchMode; private final Set<String> excludedProperties = new HashSet<String>(); /** * Create a new Example criterion instance, which includes all non-null properties by default * * @param exampleEntity The example bean to use. * * @return a new instance of Example */ public static Example create(Object exampleEntity) { if (exampleEntity == null) { throw new NullPointerException("null example entity"); } return new Example(exampleEntity, NotNullPropertySelector.INSTANCE); } /** * Allow subclasses to instantiate as needed. * * @param exampleEntity The example bean * @param selector The property selector to use */ protected Example(Object exampleEntity, PropertySelector selector) { this.exampleEntity = exampleEntity; this.selector = selector; } /** * Set escape character for "like" clause if like matching was enabled * * @param escapeCharacter The escape character * * @return {@code this}, for method chaining * * @see #enableLike */ public Example setEscapeCharacter(Character escapeCharacter) { this.escapeCharacter = escapeCharacter; return this; } /** * Use the "like" operator for all string-valued properties. This form implicitly uses {@link MatchMode#EXACT} * * @return {@code this}, for method chaining */ public Example enableLike() { return enableLike(MatchMode.EXACT); } /** * Use the "like" operator for all string-valued properties * * @param matchMode The match mode to use. * * @return {@code this}, for method chaining */ public Example enableLike(MatchMode matchMode) { this.isLikeEnabled = true; this.matchMode = matchMode; return this; } /** * Ignore case for all string-valued properties * * @return {@code this}, for method chaining */ public Example ignoreCase() { this.isIgnoreCaseEnabled = true; return this; } /** * Set the property selector to use. * * The property selector operates separate from excluding a property. * * @param selector The selector to use * * @return {@code this}, for method chaining * * @see #excludeProperty */ public Example setPropertySelector(PropertySelector selector) { this.selector = selector; return this; } /** * Exclude zero-valued properties. * * Equivalent to calling {@link #setPropertySelector} passing in {@link NotNullOrZeroPropertySelector#INSTANCE} * * @return {@code this}, for method chaining * * @see #setPropertySelector */ public Example excludeZeroes() { setPropertySelector(NotNullOrZeroPropertySelector.INSTANCE); return this; } /** * Include all properties. * * Equivalent to calling {@link #setPropertySelector} passing in {@link AllPropertySelector#INSTANCE} * * @return {@code this}, for method chaining * * @see #setPropertySelector */ public Example excludeNone() { setPropertySelector(AllPropertySelector.INSTANCE); return this; } /** * Exclude a particular property by name. * * @param name The name of the property to exclude * * @return {@code this}, for method chaining * * @see #setPropertySelector */ public Example excludeProperty(String name) { excludedProperties.add(name); return this; } @Override public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) { final StringBuilder buf = new StringBuilder().append('('); final EntityPersister meta = criteriaQuery.getFactory() .getEntityPersister(criteriaQuery.getEntityName(criteria)); final String[] propertyNames = meta.getPropertyNames(); final Type[] propertyTypes = meta.getPropertyTypes(); final Object[] propertyValues = meta.getPropertyValues(exampleEntity); for (int i = 0; i < propertyNames.length; i++) { final Object propertyValue = propertyValues[i]; final String propertyName = propertyNames[i]; final boolean isVersionProperty = i == meta.getVersionProperty(); if (!isVersionProperty && isPropertyIncluded(propertyValue, propertyName, propertyTypes[i])) { if (propertyTypes[i].isComponentType()) { appendComponentCondition(propertyName, propertyValue, (CompositeType) propertyTypes[i], criteria, criteriaQuery, buf); } else { appendPropertyCondition(propertyName, propertyValue, criteria, criteriaQuery, buf); } } } if (buf.length() == 1) { buf.append("1=1"); } return buf.append(')').toString(); } @SuppressWarnings("SimplifiableIfStatement") private boolean isPropertyIncluded(Object value, String name, Type type) { if (excludedProperties.contains(name)) { // was explicitly excluded return false; } if (type.isAssociationType()) { // associations are implicitly excluded return false; } return selector.include(value, name, type); } @Override public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery) { final EntityPersister meta = criteriaQuery.getFactory() .getEntityPersister(criteriaQuery.getEntityName(criteria)); final String[] propertyNames = meta.getPropertyNames(); final Type[] propertyTypes = meta.getPropertyTypes(); final Object[] values = meta.getPropertyValues(exampleEntity); final List<TypedValue> list = new ArrayList<TypedValue>(); for (int i = 0; i < propertyNames.length; i++) { final Object value = values[i]; final Type type = propertyTypes[i]; final String name = propertyNames[i]; final boolean isVersionProperty = i == meta.getVersionProperty(); if (!isVersionProperty && isPropertyIncluded(value, name, type)) { if (propertyTypes[i].isComponentType()) { addComponentTypedValues(name, value, (CompositeType) type, list, criteria, criteriaQuery); } else { addPropertyTypedValue(value, type, list); } } } return list.toArray(new TypedValue[list.size()]); } protected void addPropertyTypedValue(Object value, Type type, List<TypedValue> list) { if (value != null) { if (value instanceof String) { String string = (String) value; if (isIgnoreCaseEnabled) { string = string.toLowerCase(Locale.ROOT); } if (isLikeEnabled) { string = matchMode.toMatchString(string); } value = string; } list.add(new TypedValue(type, value)); } } protected void addComponentTypedValues(String path, Object component, CompositeType type, List<TypedValue> list, Criteria criteria, CriteriaQuery criteriaQuery) { if (component != null) { final String[] propertyNames = type.getPropertyNames(); final Type[] subtypes = type.getSubtypes(); final Object[] values = type.getPropertyValues(component, getEntityMode(criteria, criteriaQuery)); for (int i = 0; i < propertyNames.length; i++) { final Object value = values[i]; final Type subtype = subtypes[i]; final String subpath = StringHelper.qualify(path, propertyNames[i]); if (isPropertyIncluded(value, subpath, subtype)) { if (subtype.isComponentType()) { addComponentTypedValues(subpath, value, (CompositeType) subtype, list, criteria, criteriaQuery); } else { addPropertyTypedValue(value, subtype, list); } } } } } private EntityMode getEntityMode(Criteria criteria, CriteriaQuery criteriaQuery) { final EntityPersister meta = criteriaQuery.getFactory() .getEntityPersister(criteriaQuery.getEntityName(criteria)); final EntityMode result = meta.getEntityMode(); if (!meta.getEntityMetamodel().getTuplizer().isInstance(exampleEntity)) { throw new ClassCastException(exampleEntity.getClass().getName()); } return result; } protected void appendPropertyCondition(String propertyName, Object propertyValue, Criteria criteria, CriteriaQuery cq, StringBuilder buf) { final Criterion condition; if (propertyValue != null) { final boolean isString = propertyValue instanceof String; if (isLikeEnabled && isString) { condition = new LikeExpression(propertyName, (String) propertyValue, matchMode, escapeCharacter, isIgnoreCaseEnabled); } else { condition = new SimpleExpression(propertyName, propertyValue, "=", isIgnoreCaseEnabled && isString); } } else { condition = new NullExpression(propertyName); } final String conditionFragment = condition.toSqlString(criteria, cq); if (conditionFragment.trim().length() > 0) { if (buf.length() > 1) { buf.append(" and "); } buf.append(conditionFragment); } } protected void appendComponentCondition(String path, Object component, CompositeType type, Criteria criteria, CriteriaQuery criteriaQuery, StringBuilder buf) { if (component != null) { final String[] propertyNames = type.getPropertyNames(); final Object[] values = type.getPropertyValues(component, getEntityMode(criteria, criteriaQuery)); final Type[] subtypes = type.getSubtypes(); for (int i = 0; i < propertyNames.length; i++) { final String subPath = StringHelper.qualify(path, propertyNames[i]); final Object value = values[i]; if (isPropertyIncluded(value, subPath, subtypes[i])) { final Type subtype = subtypes[i]; if (subtype.isComponentType()) { appendComponentCondition(subPath, value, (CompositeType) subtype, criteria, criteriaQuery, buf); } else { appendPropertyCondition(subPath, value, criteria, criteriaQuery, buf); } } } } } @Override public String toString() { return "example (" + exampleEntity + ')'; } // PropertySelector definitions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** * A strategy for choosing property values for inclusion in the query criteria. Note that * property selection (for inclusion) operates separately from excluding a property. Excluded * properties are not even passed in to the PropertySelector for consideration. */ public static interface PropertySelector extends Serializable { /** * Determine whether the given property should be used in the criteria. * * @param propertyValue The property value (from the example bean) * @param propertyName The name of the property * @param type The type of the property * * @return {@code true} indicates the property should be included; {@code false} indiates it should not. */ public boolean include(Object propertyValue, String propertyName, Type type); } /** * Property selector that includes all properties */ public static final class AllPropertySelector implements PropertySelector { /** * Singleton access */ public static final AllPropertySelector INSTANCE = new AllPropertySelector(); @Override public boolean include(Object object, String propertyName, Type type) { return true; } private Object readResolve() { return INSTANCE; } } /** * Property selector that includes only properties that are not {@code null} */ public static final class NotNullPropertySelector implements PropertySelector { /** * Singleton access */ public static final NotNullPropertySelector INSTANCE = new NotNullPropertySelector(); @Override public boolean include(Object object, String propertyName, Type type) { return object != null; } private Object readResolve() { return INSTANCE; } } /** * Property selector that includes only properties that are not {@code null} and non-zero (if numeric) */ public static final class NotNullOrZeroPropertySelector implements PropertySelector { /** * Singleton access */ public static final NotNullOrZeroPropertySelector INSTANCE = new NotNullOrZeroPropertySelector(); @Override public boolean include(Object object, String propertyName, Type type) { return object != null && (!(object instanceof Number) || ((Number) object).longValue() != 0); } private Object readResolve() { return INSTANCE; } } }