Support case-insensitive null literals in SpEL
Prior to this commit null literals in SpEL expressions had to be specified as "null" (i.e., all lowercase). With this commit null literals in SpEL expressions are interpreted in a case-insensitive manner, analogous to the current support for boolean literals. Issue: SPR-9613
This commit is contained in:
parent
529e62921d
commit
a7418f480c
|
@ -513,7 +513,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
|||
private boolean maybeEatNullReference() {
|
||||
if (peekToken(TokenKind.IDENTIFIER)) {
|
||||
Token nullToken = peekToken();
|
||||
if (!nullToken.stringValue().equals("null")) {
|
||||
if (!nullToken.stringValue().toLowerCase().equals("null")) {
|
||||
return false;
|
||||
}
|
||||
nextToken();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,12 +16,13 @@
|
|||
|
||||
package org.springframework.expression.spel;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.Expression;
|
||||
|
@ -38,6 +39,7 @@ import org.springframework.expression.spel.testresources.TestPerson;
|
|||
*
|
||||
* @author Andy Clement
|
||||
* @author Mark Fisher
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
public class EvaluationTests extends ExpressionTestCase {
|
||||
|
@ -49,76 +51,66 @@ public class EvaluationTests extends ExpressionTestCase {
|
|||
TestClass testClass = new TestClass();
|
||||
Object o = null;
|
||||
o = expression.getValue(new StandardEvaluationContext(testClass));
|
||||
Assert.assertEquals("",o);
|
||||
assertEquals("", o);
|
||||
o = parser.parseExpression("list[3]").getValue(new StandardEvaluationContext(testClass));
|
||||
Assert.assertEquals("",o);
|
||||
Assert.assertEquals(4, testClass.list.size());
|
||||
assertEquals("", o);
|
||||
assertEquals(4, testClass.list.size());
|
||||
try {
|
||||
o = parser.parseExpression("list2[3]").getValue(new StandardEvaluationContext(testClass));
|
||||
Assert.fail();
|
||||
fail();
|
||||
} catch (EvaluationException ee) {
|
||||
ee.printStackTrace();
|
||||
// success!
|
||||
}
|
||||
o = parser.parseExpression("foo[3]").getValue(new StandardEvaluationContext(testClass));
|
||||
Assert.assertEquals("",o);
|
||||
Assert.assertEquals(4, testClass.getFoo().size());
|
||||
assertEquals("", o);
|
||||
assertEquals(4, testClass.getFoo().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateMapsOnAttemptToIndexNull01() throws EvaluationException, ParseException {
|
||||
|
||||
@Test(expected = SpelEvaluationException.class)
|
||||
public void testCreateMapsOnAttemptToIndexNull01() throws Exception {
|
||||
TestClass testClass = new TestClass();
|
||||
StandardEvaluationContext ctx = new StandardEvaluationContext(testClass);
|
||||
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||
Object o = null;
|
||||
o = parser.parseExpression("map['a']").getValue(ctx);
|
||||
Assert.assertNull(o);
|
||||
assertNull(o);
|
||||
o = parser.parseExpression("map").getValue(ctx);
|
||||
Assert.assertNotNull(o);
|
||||
|
||||
try {
|
||||
o = parser.parseExpression("map2['a']").getValue(ctx);
|
||||
// fail!
|
||||
Assert.fail("map2 should be null, there is no setter");
|
||||
} catch (Exception e) {
|
||||
// success!
|
||||
}
|
||||
assertNotNull(o);
|
||||
|
||||
o = parser.parseExpression("map2['a']").getValue(ctx);
|
||||
// map2 should be null, there is no setter
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateObjectsOnAttemptToReferenceNull() throws EvaluationException, ParseException {
|
||||
// wibble2 should be null (cannot be initialized dynamically), there is no setter
|
||||
@Test(expected = SpelEvaluationException.class)
|
||||
public void testCreateObjectsOnAttemptToReferenceNull() throws Exception {
|
||||
TestClass testClass = new TestClass();
|
||||
StandardEvaluationContext ctx = new StandardEvaluationContext(testClass);
|
||||
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||
Object o = null;
|
||||
o = parser.parseExpression("wibble.bar").getValue(ctx);
|
||||
Assert.assertEquals("hello",o);
|
||||
assertEquals("hello", o);
|
||||
o = parser.parseExpression("wibble").getValue(ctx);
|
||||
Assert.assertNotNull(o);
|
||||
|
||||
try {
|
||||
o = parser.parseExpression("wibble2.bar").getValue(ctx);
|
||||
// fail!
|
||||
Assert.fail("wibble2 should be null (cannot be initialized dynamically), there is no setter");
|
||||
} catch (Exception e) {
|
||||
// success!
|
||||
}
|
||||
assertNotNull(o);
|
||||
|
||||
o = parser.parseExpression("wibble2.bar").getValue(ctx);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static class TestClass {
|
||||
|
||||
|
||||
public Foo wibble;
|
||||
private Foo wibble2;
|
||||
public Map map;
|
||||
public Map<String,Integer> mapStringToInteger;
|
||||
public Map<String, Integer> mapStringToInteger;
|
||||
public List<String> list;
|
||||
public List list2;
|
||||
private Map map2;
|
||||
|
||||
private List<String> foo;
|
||||
|
||||
public Map getMap2() { return this.map2; }
|
||||
public Foo getWibble2() { return this.wibble2; }
|
||||
// public void setMap2(Map m) { this.map2 = m; }
|
||||
private List<String> foo;
|
||||
public List<String> getFoo() { return this.foo; }
|
||||
public void setFoo(List<String> newfoo) { this.foo = newfoo; }
|
||||
}
|
||||
|
@ -127,18 +119,19 @@ public class EvaluationTests extends ExpressionTestCase {
|
|||
public Foo() {}
|
||||
public String bar = "hello";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testElvis01() {
|
||||
evaluate("'Andy'?:'Dave'","Andy",String.class);
|
||||
evaluate("null?:'Dave'","Dave",String.class);
|
||||
evaluate("'Andy'?:'Dave'", "Andy", String.class);
|
||||
evaluate("null?:'Dave'", "Dave", String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSafeNavigation() {
|
||||
evaluate("null?.null?.null",null,null);
|
||||
evaluate("null?.null?.null", null, null);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRelOperatorGT01() {
|
||||
evaluate("3 > 6", "false", Boolean.class);
|
||||
|
@ -163,7 +156,7 @@ public class EvaluationTests extends ExpressionTestCase {
|
|||
public void testRelOperatorGE02() {
|
||||
evaluate("3 >= 3", "true", Boolean.class);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRelOperatorsInstanceof01() {
|
||||
evaluate("'xyz' instanceof T(int)", "false", Boolean.class);
|
||||
|
@ -218,12 +211,12 @@ public class EvaluationTests extends ExpressionTestCase {
|
|||
// property access
|
||||
@Test
|
||||
public void testPropertyField01() {
|
||||
evaluate("name", "Nikola Tesla", String.class, false);
|
||||
evaluate("name", "Nikola Tesla", String.class, false);
|
||||
// not writable because (1) name is private (2) there is no setter, only a getter
|
||||
evaluateAndCheckError("madeup", SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE, 0, "madeup",
|
||||
"org.springframework.expression.spel.testresources.Inventor");
|
||||
"org.springframework.expression.spel.testresources.Inventor");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPropertyField02_SPR7100() {
|
||||
evaluate("_name", "Nikola Tesla", String.class);
|
||||
|
@ -234,15 +227,14 @@ public class EvaluationTests extends ExpressionTestCase {
|
|||
public void testRogueTrailingDotCausesNPE_SPR6866() {
|
||||
try {
|
||||
new SpelExpressionParser().parseExpression("placeOfBirth.foo.");
|
||||
Assert.fail("Should have failed to parse");
|
||||
fail("Should have failed to parse");
|
||||
} catch (ParseException e) {
|
||||
Assert.assertTrue(e instanceof SpelParseException);
|
||||
SpelParseException spe = (SpelParseException)e;
|
||||
Assert.assertEquals(SpelMessage.OOD,spe.getMessageCode());
|
||||
Assert.assertEquals(16,spe.getPosition());
|
||||
}
|
||||
assertTrue(e instanceof SpelParseException);
|
||||
SpelParseException spe = (SpelParseException) e;
|
||||
assertEquals(SpelMessage.OOD, spe.getMessageCode());
|
||||
assertEquals(16, spe.getPosition());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// nested properties
|
||||
@Test
|
||||
|
@ -259,13 +251,13 @@ public class EvaluationTests extends ExpressionTestCase {
|
|||
public void testPropertiesNested03() throws ParseException {
|
||||
try {
|
||||
new SpelExpressionParser().parseRaw("placeOfBirth.23");
|
||||
Assert.fail();
|
||||
fail();
|
||||
} catch (SpelParseException spe) {
|
||||
Assert.assertEquals(spe.getMessageCode(), SpelMessage.UNEXPECTED_DATA_AFTER_DOT);
|
||||
Assert.assertEquals("23", spe.getInserts()[0]);
|
||||
assertEquals(spe.getMessageCode(), SpelMessage.UNEXPECTED_DATA_AFTER_DOT);
|
||||
assertEquals("23", spe.getInserts()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// methods
|
||||
@Test
|
||||
public void testMethods01() {
|
||||
|
@ -287,21 +279,21 @@ public class EvaluationTests extends ExpressionTestCase {
|
|||
public void testConstructorInvocation05() {
|
||||
evaluate("new java.lang.String('foobar')", "foobar", String.class);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testConstructorInvocation06() throws Exception {
|
||||
// repeated evaluation to drive use of cached executor
|
||||
SpelExpression expr = (SpelExpression)parser.parseExpression("new String('wibble')");
|
||||
SpelExpression expr = (SpelExpression) parser.parseExpression("new String('wibble')");
|
||||
String newString = expr.getValue(String.class);
|
||||
Assert.assertEquals("wibble",newString);
|
||||
assertEquals("wibble", newString);
|
||||
newString = expr.getValue(String.class);
|
||||
Assert.assertEquals("wibble",newString);
|
||||
|
||||
assertEquals("wibble", newString);
|
||||
|
||||
// not writable
|
||||
Assert.assertFalse(expr.isWritable(new StandardEvaluationContext()));
|
||||
|
||||
assertFalse(expr.isWritable(new StandardEvaluationContext()));
|
||||
|
||||
// ast
|
||||
Assert.assertEquals("new String('wibble')",expr.toStringAST());
|
||||
assertEquals("new String('wibble')", expr.toStringAST());
|
||||
}
|
||||
|
||||
// unary expressions
|
||||
|
@ -358,38 +350,39 @@ public class EvaluationTests extends ExpressionTestCase {
|
|||
|
||||
@Test
|
||||
public void testTernaryOperator01() {
|
||||
evaluate("2>4?1:2",2,Integer.class);
|
||||
evaluate("2>4?1:2", 2, Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTernaryOperator02() {
|
||||
evaluate("'abc'=='abc'?1:2",1,Integer.class);
|
||||
evaluate("'abc'=='abc'?1:2", 1, Integer.class);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTernaryOperator03() {
|
||||
evaluateAndCheckError("'hello'?1:2", SpelMessage.TYPE_CONVERSION_ERROR); // cannot convert String to boolean
|
||||
// cannot convert String to boolean
|
||||
evaluateAndCheckError("'hello'?1:2", SpelMessage.TYPE_CONVERSION_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTernaryOperator04() throws Exception {
|
||||
Expression expr = parser.parseExpression("1>2?3:4");
|
||||
Assert.assertFalse(expr.isWritable(eContext));
|
||||
assertFalse(expr.isWritable(eContext));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTernaryOperator05() {
|
||||
evaluate("1>2?#var=4:#var=5",5,Integer.class);
|
||||
evaluate("3?:#var=5",3,Integer.class);
|
||||
evaluate("null?:#var=5",5,Integer.class);
|
||||
evaluate("2>4?(3>2?true:false):(5<3?true:false)",false,Boolean.class);
|
||||
evaluate("1>2?#var=4:#var=5", 5, Integer.class);
|
||||
evaluate("3?:#var=5", 3, Integer.class);
|
||||
evaluate("null?:#var=5", 5, Integer.class);
|
||||
evaluate("2>4?(3>2?true:false):(5<3?true:false)", false, Boolean.class);
|
||||
}
|
||||
|
||||
@Test(expected = EvaluationException.class)
|
||||
public void testTernaryOperatorWithNullValue() {
|
||||
parser.parseExpression("null ? 0 : 1").getValue();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void methodCallWithRootReferenceThroughParameter() {
|
||||
evaluate("placeOfBirth.doubleIt(inventions.length)", 18, Integer.class);
|
||||
|
@ -397,28 +390,29 @@ public class EvaluationTests extends ExpressionTestCase {
|
|||
|
||||
@Test
|
||||
public void ctorCallWithRootReferenceThroughParameter() {
|
||||
evaluate("new org.springframework.expression.spel.testresources.PlaceOfBirth(inventions[0].toString()).city", "Telephone repeater", String.class);
|
||||
evaluate("new org.springframework.expression.spel.testresources.PlaceOfBirth(inventions[0].toString()).city",
|
||||
"Telephone repeater", String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fnCallWithRootReferenceThroughParameter() {
|
||||
evaluate("#reverseInt(inventions.length, inventions.length, inventions.length)", "int[3]{9,9,9}", int[].class);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void methodCallWithRootReferenceThroughParameterThatIsAFunctionCall() {
|
||||
evaluate("placeOfBirth.doubleIt(#reverseInt(inventions.length,2,3)[2])", 18, Integer.class);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testIndexer03() {
|
||||
evaluate("'christian'[8]", "n", String.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testIndexerError() {
|
||||
evaluateAndCheckError("new org.springframework.expression.spel.testresources.Inventor().inventions[1]",SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE);
|
||||
evaluateAndCheckError("new org.springframework.expression.spel.testresources.Inventor().inventions[1]",
|
||||
SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -447,16 +441,16 @@ public class EvaluationTests extends ExpressionTestCase {
|
|||
public void testTypeReferences01() {
|
||||
evaluate("T(java.lang.String)", "class java.lang.String", Class.class);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTypeReferencesAndQualifiedIdentifierCaching() throws Exception {
|
||||
SpelExpression expr = (SpelExpression)parser.parseExpression("T(java.lang.String)");
|
||||
Assert.assertFalse(expr.isWritable(new StandardEvaluationContext()));
|
||||
Assert.assertEquals("T(java.lang.String)",expr.toStringAST());
|
||||
Assert.assertEquals(String.class,expr.getValue(Class.class));
|
||||
SpelExpression expr = (SpelExpression) parser.parseExpression("T(java.lang.String)");
|
||||
assertFalse(expr.isWritable(new StandardEvaluationContext()));
|
||||
assertEquals("T(java.lang.String)", expr.toStringAST());
|
||||
assertEquals(String.class, expr.getValue(Class.class));
|
||||
// use cached QualifiedIdentifier:
|
||||
Assert.assertEquals("T(java.lang.String)",expr.toStringAST());
|
||||
Assert.assertEquals(String.class,expr.getValue(Class.class));
|
||||
assertEquals("T(java.lang.String)", expr.toStringAST());
|
||||
assertEquals(String.class, expr.getValue(Class.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -490,49 +484,50 @@ public class EvaluationTests extends ExpressionTestCase {
|
|||
evaluateAndAskForReturnType("3*4+5", "17", String.class);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAdvancedNumerics() throws Exception {
|
||||
int twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Integer.class);
|
||||
Assert.assertEquals(24,twentyFour);
|
||||
assertEquals(24, twentyFour);
|
||||
double one = parser.parseExpression("8.0 / 5e0 % 2").getValue(Double.class);
|
||||
Assert.assertEquals(1.6d,one);
|
||||
assertEquals(1.6d, one, 0);
|
||||
int o = parser.parseExpression("8.0 / 5e0 % 2").getValue(Integer.class);
|
||||
Assert.assertEquals(1,o);
|
||||
assertEquals(1, o);
|
||||
int sixteen = parser.parseExpression("-2 ^ 4").getValue(Integer.class);
|
||||
Assert.assertEquals(16,sixteen);
|
||||
assertEquals(16, sixteen);
|
||||
int minusFortyFive = parser.parseExpression("1+2-3*8^2/2/2").getValue(Integer.class);
|
||||
Assert.assertEquals(-45,minusFortyFive);
|
||||
assertEquals(-45, minusFortyFive);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testComparison() throws Exception {
|
||||
EvaluationContext context = TestScenarioCreator.getTestEvaluationContext();
|
||||
boolean trueValue = parser.parseExpression("T(java.util.Date) == Birthdate.Class").getValue(context, Boolean.class);
|
||||
Assert.assertTrue(trueValue);
|
||||
boolean trueValue = parser.parseExpression("T(java.util.Date) == Birthdate.Class").getValue(context,
|
||||
Boolean.class);
|
||||
assertTrue(trueValue);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testResolvingList() throws Exception {
|
||||
StandardEvaluationContext context = TestScenarioCreator.getTestEvaluationContext();
|
||||
try {
|
||||
Assert.assertFalse(parser.parseExpression("T(List)!=null").getValue(context, Boolean.class));
|
||||
Assert.fail("should have failed to find List");
|
||||
assertFalse(parser.parseExpression("T(List)!=null").getValue(context, Boolean.class));
|
||||
fail("should have failed to find List");
|
||||
} catch (EvaluationException ee) {
|
||||
// success - List not found
|
||||
}
|
||||
((StandardTypeLocator)context.getTypeLocator()).registerImport("java.util");
|
||||
Assert.assertTrue(parser.parseExpression("T(List)!=null").getValue(context, Boolean.class));
|
||||
((StandardTypeLocator) context.getTypeLocator()).registerImport("java.util");
|
||||
assertTrue(parser.parseExpression("T(List)!=null").getValue(context, Boolean.class));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testResolvingString() throws Exception {
|
||||
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
|
||||
Assert.assertEquals(String.class,stringClass);
|
||||
}
|
||||
|
||||
Class<?> stringClass = parser.parseExpression("T(String)").getValue(Class.class);
|
||||
assertEquals(String.class, stringClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* SPR-6984: attempting to index a collection on write using an index that doesn't currently exist in the collection (address.crossStreets[0] below)
|
||||
* SPR-6984: attempting to index a collection on write using an index that
|
||||
* doesn't currently exist in the collection (address.crossStreets[0] below)
|
||||
*/
|
||||
@Test
|
||||
public void initializingCollectionElementsOnWrite() throws Exception {
|
||||
|
@ -542,20 +537,38 @@ public class EvaluationTests extends ExpressionTestCase {
|
|||
ExpressionParser parser = new SpelExpressionParser(config);
|
||||
Expression expression = parser.parseExpression("name");
|
||||
expression.setValue(context, "Oleg");
|
||||
Assert.assertEquals("Oleg",person.getName());
|
||||
assertEquals("Oleg", person.getName());
|
||||
|
||||
expression = parser.parseExpression("address.street");
|
||||
expression.setValue(context, "123 High St");
|
||||
Assert.assertEquals("123 High St",person.getAddress().getStreet());
|
||||
|
||||
assertEquals("123 High St", person.getAddress().getStreet());
|
||||
|
||||
expression = parser.parseExpression("address.crossStreets[0]");
|
||||
expression.setValue(context, "Blah");
|
||||
Assert.assertEquals("Blah",person.getAddress().getCrossStreets().get(0));
|
||||
|
||||
assertEquals("Blah", person.getAddress().getCrossStreets().get(0));
|
||||
|
||||
expression = parser.parseExpression("address.crossStreets[3]");
|
||||
expression.setValue(context, "Wibble");
|
||||
Assert.assertEquals("Blah",person.getAddress().getCrossStreets().get(0));
|
||||
Assert.assertEquals("Wibble",person.getAddress().getCrossStreets().get(3));
|
||||
assertEquals("Blah", person.getAddress().getCrossStreets().get(0));
|
||||
assertEquals("Wibble", person.getAddress().getCrossStreets().get(3));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies behavior requested in SPR-9613.
|
||||
*/
|
||||
@Test
|
||||
public void caseInsensitiveNullLiterals() {
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
Expression exp;
|
||||
|
||||
exp = parser.parseExpression("null");
|
||||
assertNull(exp.getValue());
|
||||
|
||||
exp = parser.parseExpression("NULL");
|
||||
assertNull(exp.getValue());
|
||||
|
||||
exp = parser.parseExpression("NuLl");
|
||||
assertNull(exp.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ Changes in version 3.2 M2 (2012-08-xx)
|
|||
* spring-test module now depends on junit:junit-dep (SPR-6966)
|
||||
* now inferring return type of parameterized factory methods (SPR-9493)
|
||||
* SpEL Tokenizer now supports methods on integers (SPR-9612)
|
||||
* introduced support for case-insensitive null literals in SpEL expressions (SPR-9613)
|
||||
* now using BufferedInputStream in SimpleMetaDataReader to double performance (SPR-9528)
|
||||
* introduced "repeatCount" property in Quartz SimpleTriggerFactoryBean (SPR-9521)
|
||||
* introduced "jtaTransactionManager" property in Hibernate 4 LocalSessionFactoryBean/Builder (SPR-9480)
|
||||
|
|
Loading…
Reference in New Issue