HHH-465 - Support for NULLS FIRST/LAST
Conflicts: hibernate-core/src/main/java/org/hibernate/cfg/Settings.java hibernate-core/src/main/java/org/hibernate/criterion/Order.java Conflicts: hibernate-core/src/main/java/org/hibernate/cfg/SettingsFactory.java hibernate-core/src/main/java/org/hibernate/criterion/Order.java
This commit is contained in:
parent
3e956a7a58
commit
5bf8d84379
|
@ -119,7 +119,7 @@ List cats = sess.createCriteria(Cat.class)
|
||||||
|
|
||||||
<programlisting role="JAVA"><![CDATA[List cats = sess.createCriteria(Cat.class)
|
<programlisting role="JAVA"><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
.add( Restrictions.like("name", "F%")
|
.add( Restrictions.like("name", "F%")
|
||||||
.addOrder( Order.asc("name") )
|
.addOrder( Order.asc("name").nulls(NullPrecedence.LAST) )
|
||||||
.addOrder( Order.desc("age") )
|
.addOrder( Order.desc("age") )
|
||||||
.setMaxResults(50)
|
.setMaxResults(50)
|
||||||
.list();]]></programlisting>
|
.list();]]></programlisting>
|
||||||
|
|
|
@ -77,6 +77,12 @@
|
||||||
<entry>Forces Hibernate to order SQL updates by the primary key value of the items being updated. This
|
<entry>Forces Hibernate to order SQL updates by the primary key value of the items being updated. This
|
||||||
reduces the likelihood of transaction deadlocks in highly-concurrent systems.</entry>
|
reduces the likelihood of transaction deadlocks in highly-concurrent systems.</entry>
|
||||||
</row>
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>hibernate.order_by.default_null_ordering</entry>
|
||||||
|
<entry><para><literal>none</literal>, <literal>first</literal> or <literal>last</literal></para></entry>
|
||||||
|
<entry>Defines precedence of null values in <literal>ORDER BY</literal> clause. Defaults to
|
||||||
|
<literal>none</literal> which varies between RDBMS implementation.</entry>
|
||||||
|
</row>
|
||||||
<row>
|
<row>
|
||||||
<entry>hibernate.generate_statistics</entry>
|
<entry>hibernate.generate_statistics</entry>
|
||||||
<entry><para><literal>true</literal> or <literal>false</literal></para></entry>
|
<entry><para><literal>true</literal> or <literal>false</literal></para></entry>
|
||||||
|
|
|
@ -1427,7 +1427,9 @@
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
Individual expressions in the order-by can be qualified with either <literal>ASC</literal> (ascending) or
|
Individual expressions in the order-by can be qualified with either <literal>ASC</literal> (ascending) or
|
||||||
<literal>DESC</literal> (descending) to indicated the desired ordering direction.
|
<literal>DESC</literal> (descending) to indicated the desired ordering direction. Null values can be placed
|
||||||
|
in front or at the end of sorted set using <literal>NULLS FIRST</literal> or <literal>NULLS LAST</literal>
|
||||||
|
clause respectively.
|
||||||
</para>
|
</para>
|
||||||
<example>
|
<example>
|
||||||
<title>Order-by examples</title>
|
<title>Order-by examples</title>
|
||||||
|
|
|
@ -483,8 +483,8 @@ public class Part {
|
||||||
<literal>@javax.persistence.OrderBy</literal> to your property. This
|
<literal>@javax.persistence.OrderBy</literal> to your property. This
|
||||||
annotation takes as parameter a list of comma separated properties (of
|
annotation takes as parameter a list of comma separated properties (of
|
||||||
the target entity) and orders the collection accordingly (eg
|
the target entity) and orders the collection accordingly (eg
|
||||||
<code>firstname asc, age desc</code>), if the string is empty, the
|
<code>firstname asc, age desc, weight asc nulls last</code>), if the string
|
||||||
collection will be ordered by the primary key of the target
|
is empty, the collection will be ordered by the primary key of the target
|
||||||
entity.</para>
|
entity.</para>
|
||||||
|
|
||||||
<example>
|
<example>
|
||||||
|
|
|
@ -855,12 +855,17 @@ WHERE prod.name = 'widget'
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<programlisting><![CDATA[from DomesticCat cat
|
<programlisting><![CDATA[from DomesticCat cat
|
||||||
order by cat.name asc, cat.weight desc, cat.birthdate]]></programlisting>
|
order by cat.name asc, cat.weight desc nulls first, cat.birthdate]]></programlisting>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
The optional <literal>asc</literal> or <literal>desc</literal> indicate ascending or descending order
|
The optional <literal>asc</literal> or <literal>desc</literal> indicate ascending or descending order
|
||||||
respectively.
|
respectively.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The optional <literal>nulls first</literal> or <literal>nulls last</literal> indicate precedence of null
|
||||||
|
values while sorting.
|
||||||
|
</para>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section xml:id="queryhql-grouping" revision="1">
|
<section xml:id="queryhql-grouping" revision="1">
|
||||||
|
|
|
@ -348,7 +348,16 @@ orderClause
|
||||||
;
|
;
|
||||||
|
|
||||||
orderExprs
|
orderExprs
|
||||||
: orderExpr ( ASCENDING | DESCENDING )? (orderExprs)?
|
: orderExpr ( ASCENDING | DESCENDING )? ( nullOrdering )? (orderExprs)?
|
||||||
|
;
|
||||||
|
|
||||||
|
nullOrdering
|
||||||
|
: NULLS nullPrecedence
|
||||||
|
;
|
||||||
|
|
||||||
|
nullPrecedence
|
||||||
|
: FIRST
|
||||||
|
| LAST
|
||||||
;
|
;
|
||||||
|
|
||||||
orderExpr
|
orderExpr
|
||||||
|
|
|
@ -78,6 +78,9 @@ tokens
|
||||||
UPDATE="update";
|
UPDATE="update";
|
||||||
VERSIONED="versioned";
|
VERSIONED="versioned";
|
||||||
WHERE="where";
|
WHERE="where";
|
||||||
|
NULLS="nulls";
|
||||||
|
FIRST;
|
||||||
|
LAST;
|
||||||
|
|
||||||
// -- SQL tokens --
|
// -- SQL tokens --
|
||||||
// These aren't part of HQL, but the SQL fragment parser uses the HQL lexer, so they need to be declared here.
|
// These aren't part of HQL, but the SQL fragment parser uses the HQL lexer, so they need to be declared here.
|
||||||
|
@ -399,7 +402,7 @@ orderByClause
|
||||||
;
|
;
|
||||||
|
|
||||||
orderElement
|
orderElement
|
||||||
: expression ( ascendingOrDescending )?
|
: expression ( ascendingOrDescending )? ( nullOrdering )?
|
||||||
;
|
;
|
||||||
|
|
||||||
ascendingOrDescending
|
ascendingOrDescending
|
||||||
|
@ -407,6 +410,24 @@ ascendingOrDescending
|
||||||
| ( "desc" | "descending") { #ascendingOrDescending.setType(DESCENDING); }
|
| ( "desc" | "descending") { #ascendingOrDescending.setType(DESCENDING); }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
nullOrdering
|
||||||
|
: NULLS nullPrecedence
|
||||||
|
;
|
||||||
|
|
||||||
|
nullPrecedence
|
||||||
|
: IDENT {
|
||||||
|
if ( "first".equalsIgnoreCase( #nullPrecedence.getText() ) ) {
|
||||||
|
#nullPrecedence.setType( FIRST );
|
||||||
|
}
|
||||||
|
else if ( "last".equalsIgnoreCase( #nullPrecedence.getText() ) ) {
|
||||||
|
#nullPrecedence.setType( LAST );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new SemanticException( "Expecting 'first' or 'last', but found '" + #nullPrecedence.getText() + "' as null ordering precedence." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
//## havingClause:
|
//## havingClause:
|
||||||
//## HAVING logicalExpression;
|
//## HAVING logicalExpression;
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ package org.hibernate.sql.ordering.antlr;
|
||||||
* Antlr grammar for rendering <tt>ORDER_BY</tt> trees as described by the {@link OrderByFragmentParser}
|
* Antlr grammar for rendering <tt>ORDER_BY</tt> trees as described by the {@link OrderByFragmentParser}
|
||||||
|
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
*/
|
*/
|
||||||
class GeneratedOrderByFragmentRenderer extends TreeParser;
|
class GeneratedOrderByFragmentRenderer extends TreeParser;
|
||||||
|
|
||||||
|
@ -53,6 +54,13 @@ options {
|
||||||
/*package*/ String getRenderedFragment() {
|
/*package*/ String getRenderedFragment() {
|
||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation note: This is just a stub. OrderByFragmentRenderer contains the effective implementation.
|
||||||
|
*/
|
||||||
|
protected String renderOrderByElement(String expression, String collation, String order, String nulls) {
|
||||||
|
throw new UnsupportedOperationException("Concrete ORDER BY renderer should override this method.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
orderByFragment
|
orderByFragment
|
||||||
|
@ -61,32 +69,29 @@ orderByFragment
|
||||||
)
|
)
|
||||||
;
|
;
|
||||||
|
|
||||||
sortSpecification
|
sortSpecification { String sortKeySpec = null; String collSpec = null; String ordSpec = null; String nullOrd = null; }
|
||||||
: #(
|
: #(
|
||||||
SORT_SPEC sortKeySpecification (collationSpecification)? (orderingSpecification)?
|
SORT_SPEC sortKeySpec=sortKeySpecification (collSpec=collationSpecification)? (ordSpec=orderingSpecification)? (nullOrd=nullOrdering)?
|
||||||
|
{ out( renderOrderByElement( sortKeySpec, collSpec, ordSpec, nullOrd ) ); }
|
||||||
)
|
)
|
||||||
;
|
;
|
||||||
|
|
||||||
sortKeySpecification
|
sortKeySpecification returns [String sortKeyExp = null]
|
||||||
: #(SORT_KEY sortKey)
|
: #(SORT_KEY s:sortKey) { sortKeyExp = #s.getText(); }
|
||||||
;
|
;
|
||||||
|
|
||||||
sortKey
|
sortKey
|
||||||
: i:IDENT {
|
: IDENT
|
||||||
out( #i );
|
|
||||||
}
|
|
||||||
;
|
;
|
||||||
|
|
||||||
collationSpecification
|
collationSpecification returns [String collSpecExp = null]
|
||||||
: c:COLLATE {
|
: c:COLLATE { collSpecExp = "collate " + #c.getText(); }
|
||||||
out( " collate " );
|
|
||||||
out( c );
|
|
||||||
}
|
|
||||||
;
|
;
|
||||||
|
|
||||||
orderingSpecification
|
orderingSpecification returns [String ordSpecExp = null]
|
||||||
: o:ORDER_SPEC {
|
: o:ORDER_SPEC { ordSpecExp = #o.getText(); }
|
||||||
out( " " );
|
;
|
||||||
out( #o );
|
|
||||||
}
|
nullOrdering returns [String nullOrdExp = null]
|
||||||
|
: n:NULL_ORDER { nullOrdExp = #n.getText(); }
|
||||||
;
|
;
|
|
@ -46,6 +46,7 @@ tokens
|
||||||
ORDER_BY;
|
ORDER_BY;
|
||||||
SORT_SPEC;
|
SORT_SPEC;
|
||||||
ORDER_SPEC;
|
ORDER_SPEC;
|
||||||
|
NULL_ORDER;
|
||||||
SORT_KEY;
|
SORT_KEY;
|
||||||
EXPR_LIST;
|
EXPR_LIST;
|
||||||
DOT;
|
DOT;
|
||||||
|
@ -55,6 +56,9 @@ tokens
|
||||||
COLLATE="collate";
|
COLLATE="collate";
|
||||||
ASCENDING="asc";
|
ASCENDING="asc";
|
||||||
DESCENDING="desc";
|
DESCENDING="desc";
|
||||||
|
NULLS="nulls";
|
||||||
|
FIRST;
|
||||||
|
LAST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,7 +80,7 @@ tokens
|
||||||
* @return The text.
|
* @return The text.
|
||||||
*/
|
*/
|
||||||
protected final String extractText(AST ast) {
|
protected final String extractText(AST ast) {
|
||||||
// for some reason, within AST creation blocks "[]" I am somtimes unable to refer to the AST.getText() method
|
// for some reason, within AST creation blocks "[]" I am sometimes unable to refer to the AST.getText() method
|
||||||
// using #var (the #var is not interpreted as the rule's output AST).
|
// using #var (the #var is not interpreted as the rule's output AST).
|
||||||
return ast.getText();
|
return ast.getText();
|
||||||
}
|
}
|
||||||
|
@ -168,7 +172,7 @@ orderByFragment { trace("orderByFragment"); }
|
||||||
* the results should be sorted.
|
* the results should be sorted.
|
||||||
*/
|
*/
|
||||||
sortSpecification { trace("sortSpecification"); }
|
sortSpecification { trace("sortSpecification"); }
|
||||||
: sortKey (collationSpecification)? (orderingSpecification)? {
|
: sortKey (collationSpecification)? (orderingSpecification)? (nullOrdering)? {
|
||||||
#sortSpecification = #( [SORT_SPEC, "{sort specification}"], #sortSpecification );
|
#sortSpecification = #( [SORT_SPEC, "{sort specification}"], #sortSpecification );
|
||||||
#sortSpecification = postProcessSortSpecification( #sortSpecification );
|
#sortSpecification = postProcessSortSpecification( #sortSpecification );
|
||||||
}
|
}
|
||||||
|
@ -290,6 +294,30 @@ orderingSpecification! { trace("orderingSpecification"); }
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recognition rule for what SQL-2003 terms the <tt>null ordering</tt>; <tt>NULLS FIRST</tt> or
|
||||||
|
* <tt>NULLS LAST</tt>.
|
||||||
|
*/
|
||||||
|
nullOrdering! { trace("nullOrdering"); }
|
||||||
|
: NULLS n:nullPrecedence {
|
||||||
|
#nullOrdering = #( [NULL_ORDER, extractText( #n )] );
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
nullPrecedence { trace("nullPrecedence"); }
|
||||||
|
: IDENT {
|
||||||
|
if ( "first".equalsIgnoreCase( #nullPrecedence.getText() ) ) {
|
||||||
|
#nullPrecedence.setType( FIRST );
|
||||||
|
}
|
||||||
|
else if ( "last".equalsIgnoreCase( #nullPrecedence.getText() ) ) {
|
||||||
|
#nullPrecedence.setType( LAST );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new SemanticException( "Expecting 'first' or 'last', but found '" + #nullPrecedence.getText() + "' as null ordering precedence." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple-property-path is an IDENT followed by one or more (DOT IDENT) sequences
|
* A simple-property-path is an IDENT followed by one or more (DOT IDENT) sequences
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -27,8 +27,11 @@ options {
|
||||||
/** the buffer resulting SQL statement is written to */
|
/** the buffer resulting SQL statement is written to */
|
||||||
private StringBuilder buf = new StringBuilder();
|
private StringBuilder buf = new StringBuilder();
|
||||||
|
|
||||||
|
private boolean captureExpression = false;
|
||||||
|
private StringBuilder expr = new StringBuilder();
|
||||||
|
|
||||||
protected void out(String s) {
|
protected void out(String s) {
|
||||||
buf.append(s);
|
getStringBuilder().append( s );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,7 +75,7 @@ options {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected StringBuilder getStringBuilder() {
|
protected StringBuilder getStringBuilder() {
|
||||||
return buf;
|
return captureExpression ? expr : buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void nyi(AST n) {
|
protected void nyi(AST n) {
|
||||||
|
@ -92,6 +95,27 @@ options {
|
||||||
protected void commaBetweenParameters(String comma) {
|
protected void commaBetweenParameters(String comma) {
|
||||||
out(comma);
|
out(comma);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void captureExpressionStart() {
|
||||||
|
captureExpression = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void captureExpressionFinish() {
|
||||||
|
captureExpression = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String resetCapture() {
|
||||||
|
final String expression = expr.toString();
|
||||||
|
expr = new StringBuilder();
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation note: This is just a stub. SqlGenerator contains the effective implementation.
|
||||||
|
*/
|
||||||
|
protected String renderOrderByElement(String expression, String order, String nulls) {
|
||||||
|
throw new UnsupportedOperationException("Concrete SQL generator should override this method.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statement
|
statement
|
||||||
|
@ -152,9 +176,14 @@ whereClauseExpr
|
||||||
| booleanExpr[ false ]
|
| booleanExpr[ false ]
|
||||||
;
|
;
|
||||||
|
|
||||||
orderExprs
|
orderExprs { String ordExp = null; String ordDir = null; String ordNul = null; }
|
||||||
// TODO: remove goofy space before the comma when we don't have to regression test anymore.
|
// TODO: remove goofy space before the comma when we don't have to regression test anymore.
|
||||||
: ( expr ) (dir:orderDirection { out(" "); out(dir); })? ( {out(", "); } orderExprs)?
|
// Dialect is provided a hook to render each ORDER BY element, so the expression is being captured instead of
|
||||||
|
// printing to the SQL output directly. See Dialect#renderOrderByElement(String, String, String, NullPrecedence).
|
||||||
|
: { captureExpressionStart(); } ( expr ) { captureExpressionFinish(); ordExp = resetCapture(); }
|
||||||
|
(dir:orderDirection { ordDir = #dir.getText(); })? (ordNul=nullOrdering)?
|
||||||
|
{ out( renderOrderByElement( ordExp, ordDir, ordNul ) ); }
|
||||||
|
( {out(", "); } orderExprs )?
|
||||||
;
|
;
|
||||||
|
|
||||||
groupExprs
|
groupExprs
|
||||||
|
@ -167,6 +196,15 @@ orderDirection
|
||||||
| DESCENDING
|
| DESCENDING
|
||||||
;
|
;
|
||||||
|
|
||||||
|
nullOrdering returns [String nullOrdExp = null]
|
||||||
|
: NULLS fl:nullPrecedence { nullOrdExp = #fl.getText(); }
|
||||||
|
;
|
||||||
|
|
||||||
|
nullPrecedence
|
||||||
|
: FIRST
|
||||||
|
| LAST
|
||||||
|
;
|
||||||
|
|
||||||
whereExpr
|
whereExpr
|
||||||
// Expect the filter subtree, followed by the theta join subtree, followed by the HQL condition subtree.
|
// Expect the filter subtree, followed by the theta join subtree, followed by the HQL condition subtree.
|
||||||
// Might need parens around the HQL condition if there is more than one subtree.
|
// Might need parens around the HQL condition if there is more than one subtree.
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
package org.hibernate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines precedence of null values within {@code ORDER BY} clause.
|
||||||
|
*
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
public enum NullPrecedence {
|
||||||
|
/**
|
||||||
|
* Null precedence not specified. Relies on the RDBMS implementation.
|
||||||
|
*/
|
||||||
|
NONE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Null values appear at the beginning of the sorted collection.
|
||||||
|
*/
|
||||||
|
FIRST,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Null values appear at the end of the sorted collection.
|
||||||
|
*/
|
||||||
|
LAST;
|
||||||
|
|
||||||
|
public static NullPrecedence parse(String type) {
|
||||||
|
if ( "none".equalsIgnoreCase( type ) ) {
|
||||||
|
return NullPrecedence.NONE;
|
||||||
|
}
|
||||||
|
else if ( "first".equalsIgnoreCase( type ) ) {
|
||||||
|
return NullPrecedence.FIRST;
|
||||||
|
}
|
||||||
|
else if ( "last".equalsIgnoreCase( type ) ) {
|
||||||
|
return NullPrecedence.LAST;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NullPrecedence parse(String type, NullPrecedence defaultValue) {
|
||||||
|
final NullPrecedence value = parse( type );
|
||||||
|
return value != null ? value : defaultValue;
|
||||||
|
}
|
||||||
|
}
|
|
@ -417,6 +417,12 @@ public interface AvailableSettings {
|
||||||
*/
|
*/
|
||||||
public static final String ORDER_INSERTS = "hibernate.order_inserts";
|
public static final String ORDER_INSERTS = "hibernate.order_inserts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default precedence of null values in {@code ORDER BY} clause. Supported options: {@code none} (default),
|
||||||
|
* {@code first}, {@code last}.
|
||||||
|
*/
|
||||||
|
public static final String DEFAULT_NULL_ORDERING = "hibernate.order_by.default_null_ordering";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The EntityMode in which set the Session opened from the SessionFactory.
|
* The EntityMode in which set the Session opened from the SessionFactory.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -28,6 +28,7 @@ import java.util.Map;
|
||||||
import org.hibernate.ConnectionReleaseMode;
|
import org.hibernate.ConnectionReleaseMode;
|
||||||
import org.hibernate.EntityMode;
|
import org.hibernate.EntityMode;
|
||||||
import org.hibernate.MultiTenancyStrategy;
|
import org.hibernate.MultiTenancyStrategy;
|
||||||
|
import org.hibernate.NullPrecedence;
|
||||||
import org.hibernate.cache.spi.QueryCacheFactory;
|
import org.hibernate.cache.spi.QueryCacheFactory;
|
||||||
import org.hibernate.cache.spi.RegionFactory;
|
import org.hibernate.cache.spi.RegionFactory;
|
||||||
import org.hibernate.hql.spi.MultiTableBulkIdStrategy;
|
import org.hibernate.hql.spi.MultiTableBulkIdStrategy;
|
||||||
|
@ -83,6 +84,7 @@ public final class Settings {
|
||||||
private boolean namedQueryStartupCheckingEnabled;
|
private boolean namedQueryStartupCheckingEnabled;
|
||||||
private EntityTuplizerFactory entityTuplizerFactory;
|
private EntityTuplizerFactory entityTuplizerFactory;
|
||||||
private boolean checkNullability;
|
private boolean checkNullability;
|
||||||
|
private NullPrecedence defaultNullPrecedence;
|
||||||
private boolean initializeLazyStateOutsideTransactions;
|
private boolean initializeLazyStateOutsideTransactions;
|
||||||
// private ComponentTuplizerFactory componentTuplizerFactory; todo : HHH-3517 and HHH-1907
|
// private ComponentTuplizerFactory componentTuplizerFactory; todo : HHH-3517 and HHH-1907
|
||||||
// private BytecodeProvider bytecodeProvider;
|
// private BytecodeProvider bytecodeProvider;
|
||||||
|
@ -276,6 +278,9 @@ public final class Settings {
|
||||||
// return componentTuplizerFactory;
|
// return componentTuplizerFactory;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
public NullPrecedence getDefaultNullPrecedence() {
|
||||||
|
return defaultNullPrecedence;
|
||||||
|
}
|
||||||
|
|
||||||
// package protected setters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// package protected setters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -499,4 +504,8 @@ public final class Settings {
|
||||||
public void setDirectReferenceCacheEntriesEnabled(boolean directReferenceCacheEntriesEnabled) {
|
public void setDirectReferenceCacheEntriesEnabled(boolean directReferenceCacheEntriesEnabled) {
|
||||||
this.directReferenceCacheEntriesEnabled = directReferenceCacheEntriesEnabled;
|
this.directReferenceCacheEntriesEnabled = directReferenceCacheEntriesEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setDefaultNullPrecedence(NullPrecedence defaultNullPrecedence) {
|
||||||
|
this.defaultNullPrecedence = defaultNullPrecedence;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,12 +27,11 @@ import java.io.Serializable;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
|
|
||||||
import org.hibernate.ConnectionReleaseMode;
|
import org.hibernate.ConnectionReleaseMode;
|
||||||
import org.hibernate.EntityMode;
|
import org.hibernate.EntityMode;
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
import org.hibernate.MultiTenancyStrategy;
|
import org.hibernate.MultiTenancyStrategy;
|
||||||
|
import org.hibernate.NullPrecedence;
|
||||||
import org.hibernate.cache.internal.NoCachingRegionFactory;
|
import org.hibernate.cache.internal.NoCachingRegionFactory;
|
||||||
import org.hibernate.cache.internal.RegionFactoryInitiator;
|
import org.hibernate.cache.internal.RegionFactoryInitiator;
|
||||||
import org.hibernate.cache.internal.StandardQueryCacheFactory;
|
import org.hibernate.cache.internal.StandardQueryCacheFactory;
|
||||||
|
@ -56,6 +55,7 @@ import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
|
||||||
import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
|
import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
|
||||||
import org.hibernate.service.jta.platform.spi.JtaPlatform;
|
import org.hibernate.service.jta.platform.spi.JtaPlatform;
|
||||||
import org.hibernate.tuple.entity.EntityTuplizerFactory;
|
import org.hibernate.tuple.entity.EntityTuplizerFactory;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads configuration properties and builds a {@link Settings} instance.
|
* Reads configuration properties and builds a {@link Settings} instance.
|
||||||
|
@ -243,6 +243,14 @@ public class SettingsFactory implements Serializable {
|
||||||
}
|
}
|
||||||
settings.setOrderInsertsEnabled( orderInserts );
|
settings.setOrderInsertsEnabled( orderInserts );
|
||||||
|
|
||||||
|
String defaultNullPrecedence = ConfigurationHelper.getString(
|
||||||
|
AvailableSettings.DEFAULT_NULL_ORDERING, properties, "none", "first", "last"
|
||||||
|
);
|
||||||
|
if ( debugEnabled ) {
|
||||||
|
LOG.debugf( "Default null ordering: %s", defaultNullPrecedence );
|
||||||
|
}
|
||||||
|
settings.setDefaultNullPrecedence( NullPrecedence.parse( defaultNullPrecedence ) );
|
||||||
|
|
||||||
//Query parser settings:
|
//Query parser settings:
|
||||||
|
|
||||||
settings.setQueryTranslatorFactory( createQueryTranslatorFactory( properties, serviceRegistry ) );
|
settings.setQueryTranslatorFactory( createQueryTranslatorFactory( properties, serviceRegistry ) );
|
||||||
|
|
|
@ -28,21 +28,23 @@ import java.sql.Types;
|
||||||
|
|
||||||
import org.hibernate.Criteria;
|
import org.hibernate.Criteria;
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
|
import org.hibernate.NullPrecedence;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an order imposed upon a <tt>Criteria</tt> result set
|
* Represents an order imposed upon a <tt>Criteria</tt> result set
|
||||||
* @author Gavin King
|
* @author Gavin King
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
*/
|
*/
|
||||||
public class Order implements Serializable {
|
public class Order implements Serializable {
|
||||||
|
|
||||||
private boolean ascending;
|
private boolean ascending;
|
||||||
private boolean ignoreCase;
|
private boolean ignoreCase;
|
||||||
private String propertyName;
|
private String propertyName;
|
||||||
|
private NullPrecedence nullPrecedence;
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return propertyName + ' ' + (ascending?"asc":"desc");
|
return propertyName + ' ' + ( ascending ? "asc" : "desc" ) + ( nullPrecedence != null ? ' ' + nullPrecedence.name().toLowerCase() : "" );
|
||||||
}
|
}
|
||||||
|
|
||||||
public Order ignoreCase() {
|
public Order ignoreCase() {
|
||||||
|
@ -50,6 +52,11 @@ public class Order implements Serializable {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Order nulls(NullPrecedence nullPrecedence) {
|
||||||
|
this.nullPrecedence = nullPrecedence;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for Order.
|
* Constructor for Order.
|
||||||
*/
|
*/
|
||||||
|
@ -68,15 +75,23 @@ public class Order implements Serializable {
|
||||||
Type type = criteriaQuery.getTypeUsingProjection(criteria, propertyName);
|
Type type = criteriaQuery.getTypeUsingProjection(criteria, propertyName);
|
||||||
StringBuilder fragment = new StringBuilder();
|
StringBuilder fragment = new StringBuilder();
|
||||||
for ( int i=0; i<columns.length; i++ ) {
|
for ( int i=0; i<columns.length; i++ ) {
|
||||||
|
final StringBuilder expression = new StringBuilder();
|
||||||
SessionFactoryImplementor factory = criteriaQuery.getFactory();
|
SessionFactoryImplementor factory = criteriaQuery.getFactory();
|
||||||
boolean lower = ignoreCase && type.sqlTypes( factory )[i]==Types.VARCHAR;
|
boolean lower = ignoreCase && type.sqlTypes( factory )[i]==Types.VARCHAR;
|
||||||
if (lower) {
|
if (lower) {
|
||||||
fragment.append( factory.getDialect().getLowercaseFunction() )
|
expression.append( factory.getDialect().getLowercaseFunction() ).append('(');
|
||||||
.append('(');
|
|
||||||
}
|
}
|
||||||
fragment.append( columns[i] );
|
expression.append( columns[i] );
|
||||||
if (lower) fragment.append(')');
|
if (lower) expression.append(')');
|
||||||
fragment.append( ascending ? " asc" : " desc" );
|
fragment.append(
|
||||||
|
factory.getDialect()
|
||||||
|
.renderOrderByElement(
|
||||||
|
expression.toString(),
|
||||||
|
null,
|
||||||
|
ascending ? "asc" : "desc",
|
||||||
|
nullPrecedence != null ? nullPrecedence : factory.getSettings().getDefaultNullPrecedence()
|
||||||
|
)
|
||||||
|
);
|
||||||
if ( i<columns.length-1 ) fragment.append(", ");
|
if ( i<columns.length-1 ) fragment.append(", ");
|
||||||
}
|
}
|
||||||
return fragment.toString();
|
return fragment.toString();
|
||||||
|
|
|
@ -43,6 +43,7 @@ import org.hibernate.HibernateException;
|
||||||
import org.hibernate.LockMode;
|
import org.hibernate.LockMode;
|
||||||
import org.hibernate.LockOptions;
|
import org.hibernate.LockOptions;
|
||||||
import org.hibernate.MappingException;
|
import org.hibernate.MappingException;
|
||||||
|
import org.hibernate.NullPrecedence;
|
||||||
import org.hibernate.cfg.Environment;
|
import org.hibernate.cfg.Environment;
|
||||||
import org.hibernate.dialect.function.CastFunction;
|
import org.hibernate.dialect.function.CastFunction;
|
||||||
import org.hibernate.dialect.function.SQLFunction;
|
import org.hibernate.dialect.function.SQLFunction;
|
||||||
|
@ -2074,6 +2075,30 @@ public abstract class Dialect implements ConversionContext {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param expression The SQL order expression. In case of {@code @OrderBy} annotation user receives property placeholder
|
||||||
|
* (e.g. attribute name enclosed in '{' and '}' signs).
|
||||||
|
* @param collation Collation string in format {@code collate IDENTIFIER}, or {@code null}
|
||||||
|
* if expression has not been explicitly specified.
|
||||||
|
* @param order Order direction. Possible values: {@code asc}, {@code desc}, or {@code null}
|
||||||
|
* if expression has not been explicitly specified.
|
||||||
|
* @param nulls Nulls precedence. Default value: {@link NullPrecedence#NONE}.
|
||||||
|
* @return Renders single element of {@code ORDER BY} clause.
|
||||||
|
*/
|
||||||
|
public String renderOrderByElement(String expression, String collation, String order, NullPrecedence nulls) {
|
||||||
|
final StringBuilder orderByElement = new StringBuilder( expression );
|
||||||
|
if ( collation != null ) {
|
||||||
|
orderByElement.append( " " ).append( collation );
|
||||||
|
}
|
||||||
|
if ( order != null ) {
|
||||||
|
orderByElement.append( " " ).append( order );
|
||||||
|
}
|
||||||
|
if ( nulls != NullPrecedence.NONE ) {
|
||||||
|
orderByElement.append( " nulls " ).append( nulls.name().toLowerCase() );
|
||||||
|
}
|
||||||
|
return orderByElement.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does this dialect require that parameters appearing in the <tt>SELECT</tt> clause be wrapped in <tt>cast()</tt>
|
* Does this dialect require that parameters appearing in the <tt>SELECT</tt> clause be wrapped in <tt>cast()</tt>
|
||||||
* calls to tell the db parser the expected type.
|
* calls to tell the db parser the expected type.
|
||||||
|
|
|
@ -29,6 +29,7 @@ import java.sql.SQLException;
|
||||||
import java.sql.Types;
|
import java.sql.Types;
|
||||||
|
|
||||||
import org.hibernate.JDBCException;
|
import org.hibernate.JDBCException;
|
||||||
|
import org.hibernate.NullPrecedence;
|
||||||
import org.hibernate.cfg.Environment;
|
import org.hibernate.cfg.Environment;
|
||||||
import org.hibernate.dialect.function.NoArgSQLFunction;
|
import org.hibernate.dialect.function.NoArgSQLFunction;
|
||||||
import org.hibernate.dialect.function.StandardSQLFunction;
|
import org.hibernate.dialect.function.StandardSQLFunction;
|
||||||
|
@ -333,6 +334,24 @@ public class MySQLDialect extends Dialect {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String renderOrderByElement(String expression, String collation, String order, NullPrecedence nulls) {
|
||||||
|
final StringBuilder orderByElement = new StringBuilder();
|
||||||
|
if ( nulls != NullPrecedence.NONE ) {
|
||||||
|
// Workaround for NULLS FIRST / LAST support.
|
||||||
|
orderByElement.append( "case when " ).append( expression ).append( " is null then " );
|
||||||
|
if ( nulls == NullPrecedence.FIRST ) {
|
||||||
|
orderByElement.append( "0 else 1" );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
orderByElement.append( "1 else 0" );
|
||||||
|
}
|
||||||
|
orderByElement.append( " end, " );
|
||||||
|
}
|
||||||
|
// Nulls precedence has already been handled so passing NONE value.
|
||||||
|
orderByElement.append( super.renderOrderByElement( expression, collation, order, NullPrecedence.NONE ) );
|
||||||
|
return orderByElement.toString();
|
||||||
|
}
|
||||||
|
|
||||||
// locking support
|
// locking support
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ import antlr.RecognitionException;
|
||||||
import antlr.collections.AST;
|
import antlr.collections.AST;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import org.hibernate.NullPrecedence;
|
||||||
import org.hibernate.QueryException;
|
import org.hibernate.QueryException;
|
||||||
import org.hibernate.dialect.function.SQLFunction;
|
import org.hibernate.dialect.function.SQLFunction;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
@ -366,4 +367,10 @@ public class SqlGenerator extends SqlGeneratorBase implements ErrorReporter {
|
||||||
out( d );
|
out( d );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String renderOrderByElement(String expression, String order, String nulls) {
|
||||||
|
final NullPrecedence nullPrecedence = NullPrecedence.parse( nulls, sessionFactory.getSettings().getDefaultNullPrecedence() );
|
||||||
|
return sessionFactory.getDialect().renderOrderByElement( expression, null, order, nullPrecedence );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ package org.hibernate.internal.util.config;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
@ -81,6 +82,30 @@ public final class ConfigurationHelper {
|
||||||
return value == null ? defaultValue : value;
|
return value == null ? defaultValue : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the config value as a {@link String}.
|
||||||
|
*
|
||||||
|
* @param name The config setting name.
|
||||||
|
* @param values The map of config parameters.
|
||||||
|
* @param defaultValue The default value to use if not found.
|
||||||
|
* @param otherSupportedValues List of other supported values. Does not need to contain the default one.
|
||||||
|
*
|
||||||
|
* @return The value.
|
||||||
|
*
|
||||||
|
* @throws ConfigurationException Unsupported value provided.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static String getString(String name, Map values, String defaultValue, String ... otherSupportedValues) {
|
||||||
|
final String value = getString( name, values, defaultValue );
|
||||||
|
if ( !defaultValue.equals( value ) && ArrayHelper.indexOf( otherSupportedValues, value ) == -1 ) {
|
||||||
|
throw new ConfigurationException(
|
||||||
|
"Unsupported configuration [name=" + name + ", value=" + value + "]. " +
|
||||||
|
"Choose value between: '" + defaultValue + "', '" + StringHelper.join( "', '", otherSupportedValues ) + "'."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the config value as a boolean (default of false)
|
* Get the config value as a boolean (default of false)
|
||||||
*
|
*
|
||||||
|
|
|
@ -26,6 +26,8 @@ package org.hibernate.sql.ordering.antlr;
|
||||||
import antlr.collections.AST;
|
import antlr.collections.AST;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import org.hibernate.NullPrecedence;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.hql.internal.ast.util.ASTPrinter;
|
import org.hibernate.hql.internal.ast.util.ASTPrinter;
|
||||||
import org.hibernate.internal.util.StringHelper;
|
import org.hibernate.internal.util.StringHelper;
|
||||||
|
|
||||||
|
@ -41,6 +43,12 @@ public class OrderByFragmentRenderer extends GeneratedOrderByFragmentRenderer {
|
||||||
private static final Logger LOG = Logger.getLogger( OrderByFragmentRenderer.class.getName() );
|
private static final Logger LOG = Logger.getLogger( OrderByFragmentRenderer.class.getName() );
|
||||||
private static final ASTPrinter printer = new ASTPrinter( GeneratedOrderByFragmentRendererTokenTypes.class );
|
private static final ASTPrinter printer = new ASTPrinter( GeneratedOrderByFragmentRendererTokenTypes.class );
|
||||||
|
|
||||||
|
private final SessionFactoryImplementor sessionFactory;
|
||||||
|
|
||||||
|
public OrderByFragmentRenderer(SessionFactoryImplementor sessionFactory) {
|
||||||
|
this.sessionFactory = sessionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void out(AST ast) {
|
protected void out(AST ast) {
|
||||||
out( ( ( Node ) ast ).getRenderableText() );
|
out( ( ( Node ) ast ).getRenderableText() );
|
||||||
|
@ -75,4 +83,10 @@ public class OrderByFragmentRenderer extends GeneratedOrderByFragmentRenderer {
|
||||||
String prefix = "<-" + StringHelper.repeat( '-', (--traceDepth * 2) ) + " ";
|
String prefix = "<-" + StringHelper.repeat( '-', (--traceDepth * 2) ) + " ";
|
||||||
LOG.trace( prefix + ruleName );
|
LOG.trace( prefix + ruleName );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String renderOrderByElement(String expression, String collation, String order, String nulls) {
|
||||||
|
final NullPrecedence nullPrecedence = NullPrecedence.parse( nulls, sessionFactory.getSettings().getDefaultNullPrecedence() );
|
||||||
|
return sessionFactory.getDialect().renderOrderByElement( expression, collation, order, nullPrecedence );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@ public class OrderByFragmentTranslator {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the parsed tree to text.
|
// Render the parsed tree to text.
|
||||||
OrderByFragmentRenderer renderer = new OrderByFragmentRenderer();
|
OrderByFragmentRenderer renderer = new OrderByFragmentRenderer( context.getSessionFactory() );
|
||||||
try {
|
try {
|
||||||
renderer.orderByFragment( parser.getAST() );
|
renderer.orderByFragment( parser.getAST() );
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,18 +25,24 @@ package org.hibernate.sql;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.hibernate.QueryException;
|
import org.hibernate.QueryException;
|
||||||
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
import org.hibernate.cfg.Configuration;
|
||||||
import org.hibernate.dialect.Dialect;
|
import org.hibernate.dialect.Dialect;
|
||||||
import org.hibernate.dialect.HSQLDialect;
|
import org.hibernate.dialect.HSQLDialect;
|
||||||
import org.hibernate.dialect.function.SQLFunctionRegistry;
|
import org.hibernate.dialect.function.SQLFunctionRegistry;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.persister.entity.PropertyMapping;
|
import org.hibernate.persister.entity.PropertyMapping;
|
||||||
|
import org.hibernate.service.ServiceRegistry;
|
||||||
import org.hibernate.sql.ordering.antlr.ColumnMapper;
|
import org.hibernate.sql.ordering.antlr.ColumnMapper;
|
||||||
import org.hibernate.testing.junit4.BaseUnitTestCase;
|
|
||||||
|
|
||||||
import org.hibernate.sql.ordering.antlr.ColumnReference;
|
import org.hibernate.sql.ordering.antlr.ColumnReference;
|
||||||
import org.hibernate.sql.ordering.antlr.SqlValueReference;
|
import org.hibernate.sql.ordering.antlr.SqlValueReference;
|
||||||
|
import org.hibernate.testing.ServiceRegistryBuilder;
|
||||||
|
import org.hibernate.testing.junit4.BaseUnitTestCase;
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
@ -100,6 +106,23 @@ public class TemplateTest extends BaseUnitTestCase {
|
||||||
|
|
||||||
private static final SQLFunctionRegistry FUNCTION_REGISTRY = new SQLFunctionRegistry( DIALECT, Collections.EMPTY_MAP );
|
private static final SQLFunctionRegistry FUNCTION_REGISTRY = new SQLFunctionRegistry( DIALECT, Collections.EMPTY_MAP );
|
||||||
|
|
||||||
|
private static SessionFactoryImplementor SESSION_FACTORY = null; // Required for ORDER BY rendering.
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void buildSessionFactory() {
|
||||||
|
Configuration cfg = new Configuration();
|
||||||
|
cfg.setProperty( AvailableSettings.DIALECT, DIALECT.getClass().getName() );
|
||||||
|
ServiceRegistry serviceRegistry = ServiceRegistryBuilder.buildServiceRegistry( cfg.getProperties() );
|
||||||
|
SESSION_FACTORY = (SessionFactoryImplementor) cfg.buildSessionFactory( serviceRegistry );
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void closeSessionFactory() {
|
||||||
|
if ( SESSION_FACTORY != null ) {
|
||||||
|
SESSION_FACTORY.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSqlExtractFunction() {
|
public void testSqlExtractFunction() {
|
||||||
String fragment = "extract( year from col )";
|
String fragment = "extract( year from col )";
|
||||||
|
@ -244,6 +267,6 @@ public class TemplateTest extends BaseUnitTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String doStandardRendering(String fragment) {
|
public String doStandardRendering(String fragment) {
|
||||||
return Template.renderOrderByStringTemplate( fragment, MAPPER, null, DIALECT, FUNCTION_REGISTRY );
|
return Template.renderOrderByStringTemplate( fragment, MAPPER, SESSION_FACTORY, DIALECT, FUNCTION_REGISTRY );
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
package org.hibernate.test.annotations.onetomany;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.hibernate.Criteria;
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
import org.hibernate.cfg.Configuration;
|
||||||
|
import org.hibernate.dialect.H2Dialect;
|
||||||
|
import org.hibernate.testing.RequiresDialect;
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
@TestForIssue(jiraKey = "HHH-465")
|
||||||
|
@RequiresDialect(value = H2Dialect.class,
|
||||||
|
comment = "By default H2 places NULL values first, so testing 'NULLS LAST' expression.")
|
||||||
|
public class DefaultNullOrderingTest extends BaseCoreFunctionalTestCase {
|
||||||
|
@Override
|
||||||
|
protected void configure(Configuration configuration) {
|
||||||
|
configuration.setProperty( AvailableSettings.DEFAULT_NULL_ORDERING, "last" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class[] getAnnotatedClasses() {
|
||||||
|
return new Class[] { Monkey.class, Troop.class, Soldier.class };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHqlDefaultNullOrdering() {
|
||||||
|
Session session = openSession();
|
||||||
|
|
||||||
|
// Populating database with test data.
|
||||||
|
session.getTransaction().begin();
|
||||||
|
Monkey monkey1 = new Monkey();
|
||||||
|
monkey1.setName( null );
|
||||||
|
Monkey monkey2 = new Monkey();
|
||||||
|
monkey2.setName( "Warsaw ZOO" );
|
||||||
|
session.persist( monkey1 );
|
||||||
|
session.persist( monkey2 );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.getTransaction().begin();
|
||||||
|
List<Zoo> orderedResults = (List<Zoo>) session.createQuery( "from Monkey m order by m.name" ).list(); // Should order by NULLS LAST.
|
||||||
|
Assert.assertEquals( Arrays.asList( monkey2, monkey1 ), orderedResults );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.clear();
|
||||||
|
|
||||||
|
// Cleanup data.
|
||||||
|
session.getTransaction().begin();
|
||||||
|
session.delete( monkey1 );
|
||||||
|
session.delete( monkey2 );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAnnotationsDefaultNullOrdering() {
|
||||||
|
Session session = openSession();
|
||||||
|
|
||||||
|
// Populating database with test data.
|
||||||
|
session.getTransaction().begin();
|
||||||
|
Troop troop = new Troop();
|
||||||
|
troop.setName( "Alpha 1" );
|
||||||
|
Soldier ranger = new Soldier();
|
||||||
|
ranger.setName( "Ranger 1" );
|
||||||
|
troop.addSoldier( ranger );
|
||||||
|
Soldier sniper = new Soldier();
|
||||||
|
sniper.setName( null );
|
||||||
|
troop.addSoldier( sniper );
|
||||||
|
session.persist( troop );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.clear();
|
||||||
|
|
||||||
|
session.getTransaction().begin();
|
||||||
|
troop = (Troop) session.get( Troop.class, troop.getId() );
|
||||||
|
Iterator<Soldier> iterator = troop.getSoldiers().iterator(); // Should order by NULLS LAST.
|
||||||
|
Assert.assertEquals( ranger.getName(), iterator.next().getName() );
|
||||||
|
Assert.assertNull( iterator.next().getName() );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.clear();
|
||||||
|
|
||||||
|
// Cleanup data.
|
||||||
|
session.getTransaction().begin();
|
||||||
|
session.delete( troop );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCriteriaDefaultNullOrdering() {
|
||||||
|
Session session = openSession();
|
||||||
|
|
||||||
|
// Populating database with test data.
|
||||||
|
session.getTransaction().begin();
|
||||||
|
Monkey monkey1 = new Monkey();
|
||||||
|
monkey1.setName( null );
|
||||||
|
Monkey monkey2 = new Monkey();
|
||||||
|
monkey2.setName( "Berlin ZOO" );
|
||||||
|
session.persist( monkey1 );
|
||||||
|
session.persist( monkey2 );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.getTransaction().begin();
|
||||||
|
Criteria criteria = session.createCriteria( Monkey.class );
|
||||||
|
criteria.addOrder( org.hibernate.criterion.Order.asc( "name" ) ); // Should order by NULLS LAST.
|
||||||
|
Assert.assertEquals( Arrays.asList( monkey2, monkey1 ), criteria.list() );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.clear();
|
||||||
|
|
||||||
|
// Cleanup data.
|
||||||
|
session.getTransaction().begin();
|
||||||
|
session.delete( monkey1 );
|
||||||
|
session.delete( monkey2 );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,15 +23,27 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.test.annotations.onetomany;
|
package org.hibernate.test.annotations.onetomany;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.hibernate.Criteria;
|
||||||
|
import org.hibernate.NullPrecedence;
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.dialect.H2Dialect;
|
||||||
|
import org.hibernate.dialect.MySQLDialect;
|
||||||
|
import org.hibernate.testing.RequiresDialect;
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Emmanuel Bernard
|
* @author Emmanuel Bernard
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
*/
|
*/
|
||||||
public class OrderByTest extends BaseCoreFunctionalTestCase {
|
public class OrderByTest extends BaseCoreFunctionalTestCase {
|
||||||
@Test
|
@Test
|
||||||
|
@ -70,8 +82,189 @@ public class OrderByTest extends BaseCoreFunctionalTestCase {
|
||||||
s.close();
|
s.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestForIssue(jiraKey = "HHH-465")
|
||||||
|
@RequiresDialect(value = { H2Dialect.class, MySQLDialect.class },
|
||||||
|
comment = "By default H2 places NULL values first, so testing 'NULLS LAST' expression. " +
|
||||||
|
"For MySQL testing overridden Dialect#renderOrderByElement(String, String, String, NullPrecedence) method. " +
|
||||||
|
"MySQL does not support NULLS FIRST / LAST syntax at the moment, so transforming the expression to 'CASE WHEN ...'.")
|
||||||
|
public void testAnnotationNullsFirstLast() {
|
||||||
|
Session session = openSession();
|
||||||
|
|
||||||
|
// Populating database with test data.
|
||||||
|
session.getTransaction().begin();
|
||||||
|
Tiger tiger1 = new Tiger();
|
||||||
|
tiger1.setName( null ); // Explicitly setting null value.
|
||||||
|
Tiger tiger2 = new Tiger();
|
||||||
|
tiger2.setName( "Max" );
|
||||||
|
Monkey monkey1 = new Monkey();
|
||||||
|
monkey1.setName( "Michael" );
|
||||||
|
Monkey monkey2 = new Monkey();
|
||||||
|
monkey2.setName( null ); // Explicitly setting null value.
|
||||||
|
Zoo zoo = new Zoo( "Warsaw ZOO" );
|
||||||
|
zoo.getTigers().add( tiger1 );
|
||||||
|
zoo.getTigers().add( tiger2 );
|
||||||
|
zoo.getMonkeys().add( monkey1 );
|
||||||
|
zoo.getMonkeys().add( monkey2 );
|
||||||
|
session.persist( zoo );
|
||||||
|
session.persist( tiger1 );
|
||||||
|
session.persist( tiger2 );
|
||||||
|
session.persist( monkey1 );
|
||||||
|
session.persist( monkey2 );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.clear();
|
||||||
|
|
||||||
|
session.getTransaction().begin();
|
||||||
|
zoo = (Zoo) session.get( Zoo.class, zoo.getId() );
|
||||||
|
// Testing @org.hibernate.annotations.OrderBy.
|
||||||
|
Iterator<Tiger> iterator1 = zoo.getTigers().iterator();
|
||||||
|
Assert.assertEquals( tiger2.getName(), iterator1.next().getName() );
|
||||||
|
Assert.assertNull( iterator1.next().getName() );
|
||||||
|
// Testing @javax.persistence.OrderBy.
|
||||||
|
Iterator<Monkey> iterator2 = zoo.getMonkeys().iterator();
|
||||||
|
Assert.assertEquals( monkey1.getName(), iterator2.next().getName() );
|
||||||
|
Assert.assertNull( iterator2.next().getName() );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.clear();
|
||||||
|
|
||||||
|
// Cleanup data.
|
||||||
|
session.getTransaction().begin();
|
||||||
|
session.delete( tiger1 );
|
||||||
|
session.delete( tiger2 );
|
||||||
|
session.delete( monkey1 );
|
||||||
|
session.delete( monkey2 );
|
||||||
|
session.delete( zoo );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestForIssue(jiraKey = "HHH-465")
|
||||||
|
@RequiresDialect(value = { H2Dialect.class, MySQLDialect.class },
|
||||||
|
comment = "By default H2 places NULL values first, so testing 'NULLS LAST' expression. " +
|
||||||
|
"For MySQL testing overridden Dialect#renderOrderByElement(String, String, String, NullPrecedence) method. " +
|
||||||
|
"MySQL does not support NULLS FIRST / LAST syntax at the moment, so transforming the expression to 'CASE WHEN ...'.")
|
||||||
|
public void testCriteriaNullsFirstLast() {
|
||||||
|
Session session = openSession();
|
||||||
|
|
||||||
|
// Populating database with test data.
|
||||||
|
session.getTransaction().begin();
|
||||||
|
Zoo zoo1 = new Zoo( null );
|
||||||
|
Zoo zoo2 = new Zoo( "Warsaw ZOO" );
|
||||||
|
session.persist( zoo1 );
|
||||||
|
session.persist( zoo2 );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.clear();
|
||||||
|
|
||||||
|
session.getTransaction().begin();
|
||||||
|
Criteria criteria = session.createCriteria( Zoo.class );
|
||||||
|
criteria.addOrder( org.hibernate.criterion.Order.asc( "name" ).nulls( NullPrecedence.LAST ) );
|
||||||
|
Iterator<Zoo> iterator = (Iterator<Zoo>) criteria.list().iterator();
|
||||||
|
Assert.assertEquals( zoo2.getName(), iterator.next().getName() );
|
||||||
|
Assert.assertNull( iterator.next().getName() );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.clear();
|
||||||
|
|
||||||
|
// Cleanup data.
|
||||||
|
session.getTransaction().begin();
|
||||||
|
session.delete( zoo1 );
|
||||||
|
session.delete( zoo2 );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestForIssue(jiraKey = "HHH-465")
|
||||||
|
@RequiresDialect(value = { H2Dialect.class, MySQLDialect.class },
|
||||||
|
comment = "By default H2 places NULL values first, so testing 'NULLS LAST' expression. " +
|
||||||
|
"For MySQL testing overridden Dialect#renderOrderByElement(String, String, String, NullPrecedence) method. " +
|
||||||
|
"MySQL does not support NULLS FIRST / LAST syntax at the moment, so transforming the expression to 'CASE WHEN ...'.")
|
||||||
|
public void testNullsFirstLastSpawnMultipleColumns() {
|
||||||
|
Session session = openSession();
|
||||||
|
|
||||||
|
// Populating database with test data.
|
||||||
|
session.getTransaction().begin();
|
||||||
|
Zoo zoo = new Zoo();
|
||||||
|
zoo.setName( "Berlin ZOO" );
|
||||||
|
Visitor visitor1 = new Visitor( null, null );
|
||||||
|
Visitor visitor2 = new Visitor( null, "Antoniak" );
|
||||||
|
Visitor visitor3 = new Visitor( "Lukasz", "Antoniak" );
|
||||||
|
zoo.getVisitors().add( visitor1 );
|
||||||
|
zoo.getVisitors().add( visitor2 );
|
||||||
|
zoo.getVisitors().add( visitor3 );
|
||||||
|
session.save( zoo );
|
||||||
|
session.save( visitor1 );
|
||||||
|
session.save( visitor2 );
|
||||||
|
session.save( visitor3 );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.clear();
|
||||||
|
|
||||||
|
session.getTransaction().begin();
|
||||||
|
zoo = (Zoo) session.get( Zoo.class, zoo.getId() );
|
||||||
|
Iterator<Visitor> iterator = zoo.getVisitors().iterator();
|
||||||
|
Assert.assertEquals( 3, zoo.getVisitors().size() );
|
||||||
|
Assert.assertEquals( visitor3, iterator.next() );
|
||||||
|
Assert.assertEquals( visitor2, iterator.next() );
|
||||||
|
Assert.assertEquals( visitor1, iterator.next() );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.clear();
|
||||||
|
|
||||||
|
// Cleanup data.
|
||||||
|
session.getTransaction().begin();
|
||||||
|
session.delete( visitor1 );
|
||||||
|
session.delete( visitor2 );
|
||||||
|
session.delete( visitor3 );
|
||||||
|
session.delete( zoo );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestForIssue(jiraKey = "HHH-465")
|
||||||
|
@RequiresDialect(value = { H2Dialect.class, MySQLDialect.class },
|
||||||
|
comment = "By default H2 places NULL values first, so testing 'NULLS LAST' expression. " +
|
||||||
|
"For MySQL testing overridden Dialect#renderOrderByElement(String, String, String, NullPrecedence) method. " +
|
||||||
|
"MySQL does not support NULLS FIRST / LAST syntax at the moment, so transforming the expression to 'CASE WHEN ...'.")
|
||||||
|
public void testHqlNullsFirstLast() {
|
||||||
|
Session session = openSession();
|
||||||
|
|
||||||
|
// Populating database with test data.
|
||||||
|
session.getTransaction().begin();
|
||||||
|
Zoo zoo1 = new Zoo();
|
||||||
|
zoo1.setName( null );
|
||||||
|
Zoo zoo2 = new Zoo();
|
||||||
|
zoo2.setName( "Warsaw ZOO" );
|
||||||
|
session.persist( zoo1 );
|
||||||
|
session.persist( zoo2 );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.getTransaction().begin();
|
||||||
|
List<Zoo> orderedResults = (List<Zoo>) session.createQuery( "from Zoo z order by z.name nulls lAsT" ).list();
|
||||||
|
Assert.assertEquals( Arrays.asList( zoo2, zoo1 ), orderedResults );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.clear();
|
||||||
|
|
||||||
|
// Cleanup data.
|
||||||
|
session.getTransaction().begin();
|
||||||
|
session.delete( zoo1 );
|
||||||
|
session.delete( zoo2 );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Class[] getAnnotatedClasses() {
|
protected Class[] getAnnotatedClasses() {
|
||||||
return new Class[] { Order.class, OrderItem.class };
|
return new Class[] { Order.class, OrderItem.class, Zoo.class, Tiger.class, Monkey.class, Visitor.class };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,18 +44,20 @@ public class Soldier {
|
||||||
this.troop = troop;
|
this.troop = troop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if ( this == o ) return true;
|
if ( this == o ) return true;
|
||||||
if ( !( o instanceof Soldier ) ) return false;
|
if ( !( o instanceof Soldier ) ) return false;
|
||||||
|
|
||||||
final Soldier soldier = (Soldier) o;
|
final Soldier soldier = (Soldier) o;
|
||||||
|
|
||||||
if ( !name.equals( soldier.name ) ) return false;
|
if ( name != null ? !name.equals( soldier.name ) : soldier.name != null ) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return name.hashCode();
|
return name != null ? name.hashCode() : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
package org.hibernate.test.annotations.onetomany;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
public class Visitor implements Serializable {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String firstName;
|
||||||
|
|
||||||
|
private String lastName;
|
||||||
|
|
||||||
|
public Visitor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Visitor(String firstName, String lastName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastName() {
|
||||||
|
return lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if ( this == o ) return true;
|
||||||
|
if ( ! ( o instanceof Visitor) ) return false;
|
||||||
|
|
||||||
|
Visitor visitor = (Visitor) o;
|
||||||
|
|
||||||
|
if ( firstName != null ? !firstName.equals( visitor.firstName ) : visitor.firstName != null ) return false;
|
||||||
|
if ( id != null ? !id.equals( visitor.id ) : visitor.id != null ) return false;
|
||||||
|
if ( lastName != null ? !lastName.equals( visitor.lastName ) : visitor.lastName != null ) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = id != null ? id.hashCode() : 0;
|
||||||
|
result = 31 * result + ( firstName != null ? firstName.hashCode() : 0 );
|
||||||
|
result = 31 * result + ( lastName != null ? lastName.hashCode() : 0 );
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Visitor(id = " + id + ", firstName = " + firstName + ", lastName = " + lastName + ")";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
package org.hibernate.test.annotations.onetomany;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity used to test {@code NULL} values ordering in SQL {@code ORDER BY} clause.
|
||||||
|
* Implementation note: By default H2 places {@code NULL} values first.
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
public class Zoo implements Serializable {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@OneToMany
|
||||||
|
@JoinColumn(name = "zoo_id")
|
||||||
|
@org.hibernate.annotations.OrderBy(clause = "name asc nulls last") // By default H2 places NULL values first.
|
||||||
|
private Set<Tiger> tigers = new HashSet<Tiger>();
|
||||||
|
|
||||||
|
@OneToMany
|
||||||
|
@JoinColumn(name = "zoo_id")
|
||||||
|
@javax.persistence.OrderBy("name asc nulls last") // According to JPA specification this is illegal, but works in Hibernate.
|
||||||
|
private Set<Monkey> monkeys = new HashSet<Monkey>();
|
||||||
|
|
||||||
|
@OneToMany
|
||||||
|
@JoinColumn(name = "zoo_id")
|
||||||
|
@javax.persistence.OrderBy("lastName desc nulls last, firstName asc nulls LaSt") // Sorting by multiple columns.
|
||||||
|
private Set<Visitor> visitors = new HashSet<Visitor>();
|
||||||
|
|
||||||
|
public Zoo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Zoo(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if ( this == o ) return true;
|
||||||
|
if ( ! ( o instanceof Zoo ) ) return false;
|
||||||
|
|
||||||
|
Zoo zoo = (Zoo) o;
|
||||||
|
|
||||||
|
if ( id != null ? !id.equals( zoo.id ) : zoo.id != null ) return false;
|
||||||
|
if ( name != null ? !name.equals( zoo.name ) : zoo.name != null ) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = id != null ? id.hashCode() : 0;
|
||||||
|
result = 31 * result + ( name != null ? name.hashCode() : 0 );
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Zoo(id = " + id + ", name = " + name + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Tiger> getTigers() {
|
||||||
|
return tigers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTigers(Set<Tiger> tigers) {
|
||||||
|
this.tigers = tigers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Monkey> getMonkeys() {
|
||||||
|
return monkeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMonkeys(Set<Monkey> monkeys) {
|
||||||
|
this.monkeys = monkeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Visitor> getVisitors() {
|
||||||
|
return visitors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVisitors(Set<Visitor> visitors) {
|
||||||
|
this.visitors = visitors;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue