diff --git a/README b/README.MD similarity index 57% rename from README rename to README.MD index ce5108741..f3ea81075 100644 --- a/README +++ b/README.MD @@ -4,3 +4,8 @@ Simplicity is the biggest advantage of the MyBatis data mapper over object relat See the docs in: http://mybatis.github.io/mybatis-3 Download it from: https://github.com/mybatis/mybatis-3/releases + +# NOTICE # +This branch is a special branch that is being populated via cherry-pick or special needs from the master. +See [here](https://github.com/mybatis/mybatis-3/pull/401) and [here](https://github.com/mybatis/mybatis-3/pull/399) +for details. It should not be merged back to master. This eventually will go away. diff --git a/pom.xml b/pom.xml index 10200982e..bd689e8d4 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ mybatis - 3.2.7 + 3.2.9-SNAPSHOT jar MyBatis @@ -107,7 +107,7 @@ http://github.com/mybatis/mybatis-3 scm:git:ssh://github.com/mybatis/mybatis-3.git scm:git:git+ssh://git@github.com/mybatis/mybatis-3.git - mybatis-3.2.7 + 3.2.x GitHub Issue Management @@ -163,7 +163,7 @@ org.apache.logging.log4j log4j-core - 2.0-rc1 + 2.0.2 true diff --git a/src/main/java/org/apache/ibatis/annotations/Flush.java b/src/main/java/org/apache/ibatis/annotations/Flush.java new file mode 100644 index 000000000..b130168fa --- /dev/null +++ b/src/main/java/org/apache/ibatis/annotations/Flush.java @@ -0,0 +1,31 @@ +/** + * Copyright 2009-2015 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The maker annotation that invoke a flush statements via Mapper interface. + * + * @author Kazuki Shimizu + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Flush { +} diff --git a/src/main/java/org/apache/ibatis/binding/MapperMethod.java b/src/main/java/org/apache/ibatis/binding/MapperMethod.java index eae9c966d..b4fdc20bc 100644 --- a/src/main/java/org/apache/ibatis/binding/MapperMethod.java +++ b/src/main/java/org/apache/ibatis/binding/MapperMethod.java @@ -15,6 +15,7 @@ */ package org.apache.ibatis.binding; +import org.apache.ibatis.annotations.Flush; import org.apache.ibatis.annotations.MapKey; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.mapping.MappedStatement; @@ -67,6 +68,8 @@ public Object execute(SqlSession sqlSession, Object[] args) { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } + } else if (SqlCommandType.FLUSH == command.getType()) { + result = sqlSession.flushStatements(); } else { throw new BindingException("Unknown execution method for: " + command.getName()); } @@ -186,12 +189,18 @@ public SqlCommand(Configuration configuration, Class mapperInterface, Method } } if (ms == null) { - throw new BindingException("Invalid bound statement (not found): " + statementName); - } - name = ms.getId(); - type = ms.getSqlCommandType(); - if (type == SqlCommandType.UNKNOWN) { - throw new BindingException("Unknown execution method for: " + name); + if(method.getAnnotation(Flush.class) != null){ + name = null; + type = SqlCommandType.FLUSH; + } else { + throw new BindingException("Invalid bound statement (not found): " + statementName); + } + } else { + name = ms.getId(); + type = ms.getSqlCommandType(); + if (type == SqlCommandType.UNKNOWN) { + throw new BindingException("Unknown execution method for: " + name); + } } } diff --git a/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java b/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java index 70b4e61b1..1faedd8dc 100644 --- a/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java +++ b/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java @@ -123,7 +123,9 @@ public void parse() { Method[] methods = type.getMethods(); for (Method method : methods) { try { - parseStatement(method); + if (!method.isBridge()) { // issue #237 + parseStatement(method); + } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver(this, method)); } diff --git a/src/main/java/org/apache/ibatis/builder/xml/XMLConfigBuilder.java b/src/main/java/org/apache/ibatis/builder/xml/XMLConfigBuilder.java index 1b90dd9a0..ecd745559 100644 --- a/src/main/java/org/apache/ibatis/builder/xml/XMLConfigBuilder.java +++ b/src/main/java/org/apache/ibatis/builder/xml/XMLConfigBuilder.java @@ -206,6 +206,7 @@ private void settingsElement(XNode context) throws Exception { configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false)); configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE"))); configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null)); + configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null)); configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false)); configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false)); configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION"))); diff --git a/src/main/java/org/apache/ibatis/executor/ResultExtractor.java b/src/main/java/org/apache/ibatis/executor/ResultExtractor.java index fdc23f380..b2d67fc39 100644 --- a/src/main/java/org/apache/ibatis/executor/ResultExtractor.java +++ b/src/main/java/org/apache/ibatis/executor/ResultExtractor.java @@ -43,8 +43,16 @@ public Object extractObjectFromList(List list, Class targetType) { MetaObject metaObject = configuration.newMetaObject(value); metaObject.addAll(list); } else if (targetType != null && targetType.isArray()) { - Object[] array = (Object[]) Array.newInstance(targetType.getComponentType(), list.size()); - value = list.toArray(array); + Class arrayComponentType = targetType.getComponentType(); + Object array = Array.newInstance(arrayComponentType, list.size()); + if (arrayComponentType.isPrimitive()) { + for (int i = 0; i < list.size(); i++) { + Array.set(array, i, list.get(i)); + } + value = array; + } else { + value = list.toArray((Object[])array); + } } else { if (list != null && list.size() > 1) { throw new ExecutorException("Statement returned more than one row, where no more than one was expected."); diff --git a/src/main/java/org/apache/ibatis/executor/loader/ResultLoaderMap.java b/src/main/java/org/apache/ibatis/executor/loader/ResultLoaderMap.java index f59bd2add..c41eb8e96 100644 --- a/src/main/java/org/apache/ibatis/executor/loader/ResultLoaderMap.java +++ b/src/main/java/org/apache/ibatis/executor/loader/ResultLoaderMap.java @@ -155,10 +155,13 @@ private LoadPair(final String property, MetaObject metaResultObject, ResultLoade this.configurationFactory = resultLoader.configuration.getConfigurationFactory(); } else { - this.getLogger().debug("Property [" + this.property + "] of [" - + metaResultObject.getOriginalObject().getClass() + "] cannot be loaded " - + "after deserialization. Make sure it's loaded before serializing " - + "forenamed object."); + Log log = this.getLogger(); + if (log.isDebugEnabled()) { + log.debug("Property [" + this.property + "] of [" + + metaResultObject.getOriginalObject().getClass() + "] cannot be loaded " + + "after deserialization. Make sure it's loaded before serializing " + + "forenamed object."); + } } } } diff --git a/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibProxyFactory.java b/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibProxyFactory.java index 371253d75..ccd3bc9f8 100644 --- a/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibProxyFactory.java +++ b/src/main/java/org/apache/ibatis/executor/loader/cglib/CglibProxyFactory.java @@ -75,7 +75,9 @@ private static Object crateProxy(Class type, Callback callback, List try { type.getDeclaredMethod(WRITE_REPLACE_METHOD); // ObjectOutputStream will call writeReplace of objects returned by writeReplace - log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this"); + if (log.isDebugEnabled()) { + log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this"); + } } catch (NoSuchMethodException e) { enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class}); } catch (SecurityException e) { diff --git a/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistProxyFactory.java b/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistProxyFactory.java index 40492a999..3981af0eb 100644 --- a/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistProxyFactory.java +++ b/src/main/java/org/apache/ibatis/executor/loader/javassist/JavassistProxyFactory.java @@ -75,7 +75,9 @@ private static Object crateProxy(Class type, MethodHandler callback, List nextResultMaps = new HashMap(); - private final Map pendingRelations = new HashMap(); + private final Map> pendingRelations = new HashMap>(); private static class PendingRelation { public MetaObject metaObject; @@ -137,6 +138,8 @@ private void handleRefCursorOutputParameter(ResultSet rs, ParameterMapping param // public List handleResultSets(Statement stmt) throws SQLException { + ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); + final List multipleResults = new ArrayList(); int resultSetCount = 0; @@ -286,7 +289,7 @@ private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap r throws SQLException { DefaultResultContext resultContext = new DefaultResultContext(); skipRows(rsw.getResultSet(), rowBounds); - while (shouldProcessMoreRows(rsw.getResultSet(), resultContext, rowBounds)) { + while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) { ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); Object rowValue = getRowValue(rsw, discriminatedResultMap); storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); @@ -295,7 +298,7 @@ private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap r private void storeObject(ResultHandler resultHandler, DefaultResultContext resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException { if (parentMapping != null) { - linkToParent(rs, parentMapping, rowValue); + linkToParents(rs, parentMapping, rowValue); } else { callResultHandler(resultHandler, resultContext, rowValue); } @@ -306,8 +309,8 @@ private void callResultHandler(ResultHandler resultHandler, DefaultResultContext resultHandler.handleResult(resultContext); } - private boolean shouldProcessMoreRows(ResultSet rs, ResultContext context, RowBounds rowBounds) throws SQLException { - return !context.isStopped() && rs.next() && context.getResultCount() < rowBounds.getLimit(); + private boolean shouldProcessMoreRows(ResultContext context, RowBounds rowBounds) throws SQLException { + return !context.isStopped() && context.getResultCount() < rowBounds.getLimit(); } private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException { @@ -357,16 +360,24 @@ private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, boolean foundValues = false; final List propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { - final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); + String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); + if (propertyMapping.getNestedResultMapId() != null) { + // the user added a column attribute to a nested result map, ignore it + column = null; + } if (propertyMapping.isCompositeResult() || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) || propertyMapping.getResultSet() != null) { Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix); - final String property = propertyMapping.getProperty(); // issue #541 make property optional - if (value != NO_VALUE && property != null && (value != null || configuration.isCallSettersOnNulls())) { // issue #377, call setter on nulls - if (value != null || !metaObject.getSetterType(property).isPrimitive()) { - metaObject.setValue(property, value); - } + // issue #541 make property optional + final String property = propertyMapping.getProperty(); + // issue #377, call setter on nulls + if (value != DEFERED + && property != null + && (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive()))) { + metaObject.setValue(property, value); + } + if (value != null || value == DEFERED) { foundValues = true; } } @@ -379,11 +390,8 @@ private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject if (propertyMapping.getNestedQueryId() != null) { return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix); } else if (propertyMapping.getResultSet() != null) { - addPendingChildRelation(rs, metaResultObject, propertyMapping); - return NO_VALUE; - } else if (propertyMapping.getNestedResultMapId() != null) { - // the user added a column attribute to a nested result map, ignore it - return NO_VALUE; + addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK? + return DEFERED; } else { final TypeHandler typeHandler = propertyMapping.getTypeHandler(); final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); @@ -425,17 +433,21 @@ private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap // MULTIPLE RESULT SETS - private void linkToParent(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException { + private void linkToParents(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException { CacheKey parentKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(), parentMapping.getForeignColumn()); - PendingRelation parent = pendingRelations.get(parentKey); - if (parent != null) { - final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(parent.propertyMapping, parent.metaObject); - if (rowValue != null) { - if (collectionProperty != null) { - final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty); - targetMetaObject.add(rowValue); - } else { - parent.metaObject.setValue(parent.propertyMapping.getProperty(), rowValue); + List parents = pendingRelations.get(parentKey); + if (parents != null) { + for (PendingRelation parent : parents) { + if (parent != null) { + final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(parent.propertyMapping, parent.metaObject); + if (rowValue != null) { + if (collectionProperty != null) { + final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty); + targetMetaObject.add(rowValue); + } else { + parent.metaObject.setValue(parent.propertyMapping.getProperty(), rowValue); + } + } } } } @@ -469,7 +481,13 @@ private void addPendingChildRelation(ResultSet rs, MetaObject metaResultObject, PendingRelation deferLoad = new PendingRelation(); deferLoad.metaObject = metaResultObject; deferLoad.propertyMapping = parentMapping; - pendingRelations.put(cacheKey, deferLoad); + List relations = pendingRelations.get(cacheKey); + // issue #255 + if (relations == null) { + relations = new ArrayList(); + pendingRelations.put(cacheKey, relations); + } + relations.add(deferLoad); ResultMapping previous = nextResultMaps.get(parentMapping.getResultSet()); if (previous == null) { nextResultMaps.put(parentMapping.getResultSet(), parentMapping); @@ -593,17 +611,19 @@ private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObj final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId); final Class nestedQueryParameterType = nestedQuery.getParameterMap().getType(); final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix); - Object value = NO_VALUE; + Object value = null; if (nestedQueryParameterObject != null) { final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject); final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql); final Class targetType = propertyMapping.getJavaType(); if (executor.isCached(nestedQuery, key)) { executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType); + value = DEFERED; } else { final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql); if (propertyMapping.isLazy()) { lazyLoader.addLoader(property, metaResultObject, resultLoader); + value = DEFERED; } else { value = resultLoader.loadResult(); } @@ -699,7 +719,7 @@ private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap r final DefaultResultContext resultContext = new DefaultResultContext(); skipRows(rsw.getResultSet(), rowBounds); Object rowValue = null; - while (shouldProcessMoreRows(rsw.getResultSet(), resultContext, rowBounds)) { + while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) { final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null); Object partialObject = nestedResultObjects.get(rowKey); @@ -716,7 +736,7 @@ private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap r } } } - if (rowValue != null && mappedStatement.isResultOrdered()) { + if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) { storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } } diff --git a/src/main/java/org/apache/ibatis/executor/statement/BaseStatementHandler.java b/src/main/java/org/apache/ibatis/executor/statement/BaseStatementHandler.java index 31629c4ef..65e75d7ab 100644 --- a/src/main/java/org/apache/ibatis/executor/statement/BaseStatementHandler.java +++ b/src/main/java/org/apache/ibatis/executor/statement/BaseStatementHandler.java @@ -111,6 +111,11 @@ protected void setFetchSize(Statement stmt) throws SQLException { Integer fetchSize = mappedStatement.getFetchSize(); if (fetchSize != null) { stmt.setFetchSize(fetchSize); + return; + } + Integer defaultFetchSize = configuration.getDefaultFetchSize(); + if (defaultFetchSize != null) { + stmt.setFetchSize(defaultFetchSize); } } diff --git a/src/main/java/org/apache/ibatis/io/DefaultVFS.java b/src/main/java/org/apache/ibatis/io/DefaultVFS.java index 7b579dc40..9946f7b72 100644 --- a/src/main/java/org/apache/ibatis/io/DefaultVFS.java +++ b/src/main/java/org/apache/ibatis/io/DefaultVFS.java @@ -61,7 +61,9 @@ public List list(URL url, String path) throws IOException { URL jarUrl = findJarForResource(url); if (jarUrl != null) { is = jarUrl.openStream(); - log.debug("Listing " + url); + if (log.isDebugEnabled()) { + log.debug("Listing " + url); + } resources = listResources(new JarInputStream(is), path); } else { @@ -72,9 +74,13 @@ public List list(URL url, String path) throws IOException { // referenced by the URL isn't actually a JAR is = url.openStream(); JarInputStream jarInput = new JarInputStream(is); - log.debug("Listing " + url); + if (log.isDebugEnabled()) { + log.debug("Listing " + url); + } for (JarEntry entry; (entry = jarInput.getNextJarEntry()) != null;) { - log.debug("Jar entry: " + entry.getName()); + if (log.isDebugEnabled()) { + log.debug("Jar entry: " + entry.getName()); + } children.add(entry.getName()); } } @@ -91,7 +97,9 @@ public List list(URL url, String path) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(is)); List lines = new ArrayList(); for (String line; (line = reader.readLine()) != null;) { - log.debug("Reader entry: " + line); + if (log.isDebugEnabled()) { + log.debug("Reader entry: " + line); + } lines.add(line); if (getResources(path + "/" + line).isEmpty()) { lines.clear(); @@ -100,7 +108,9 @@ public List list(URL url, String path) throws IOException { } if (!lines.isEmpty()) { - log.debug("Listing " + url); + if (log.isDebugEnabled()) { + log.debug("Listing " + url); + } children.addAll(lines); } } @@ -112,9 +122,13 @@ public List list(URL url, String path) throws IOException { */ if ("file".equals(url.getProtocol())) { File file = new File(url.getFile()); - log.debug("Listing directory " + file.getAbsolutePath()); + if (log.isDebugEnabled()) { + log.debug("Listing directory " + file.getAbsolutePath()); + } if (file.isDirectory()) { - log.debug("Listing " + url); + if (log.isDebugEnabled()) { + log.debug("Listing " + url); + } children = Arrays.asList(file.list()); } } @@ -175,7 +189,9 @@ protected List listResources(JarInputStream jar, String path) throws IOE // Check file name if (name.startsWith(path)) { - log.debug("Found resource: " + name); + if (log.isDebugEnabled()) { + log.debug("Found resource: " + name); + } resources.add(name.substring(1)); // Trim leading slash } } @@ -194,13 +210,17 @@ protected List listResources(JarInputStream jar, String path) throws IOE * @throws MalformedURLException */ protected URL findJarForResource(URL url) throws MalformedURLException { - log.debug("Find JAR URL: " + url); + if (log.isDebugEnabled()) { + log.debug("Find JAR URL: " + url); + } // If the file part of the URL is itself a URL, then that URL probably points to the JAR try { for (;;) { url = new URL(url.getFile()); - log.debug("Inner URL: " + url); + if (log.isDebugEnabled()) { + log.debug("Inner URL: " + url); + } } } catch (MalformedURLException e) { // This will happen at some point and serves as a break in the loop @@ -211,10 +231,14 @@ protected URL findJarForResource(URL url) throws MalformedURLException { int index = jarUrl.lastIndexOf(".jar"); if (index >= 0) { jarUrl.setLength(index + 4); - log.debug("Extracted JAR URL: " + jarUrl); + if (log.isDebugEnabled()) { + log.debug("Extracted JAR URL: " + jarUrl); + } } else { - log.debug("Not a JAR: " + jarUrl); + if (log.isDebugEnabled()) { + log.debug("Not a JAR: " + jarUrl); + } return null; } @@ -226,7 +250,9 @@ protected URL findJarForResource(URL url) throws MalformedURLException { } else { // WebLogic fix: check if the URL's file exists in the filesystem. - log.debug("Not a JAR: " + jarUrl); + if (log.isDebugEnabled()) { + log.debug("Not a JAR: " + jarUrl); + } jarUrl.replace(0, jarUrl.length(), testUrl.getFile()); File file = new File(jarUrl.toString()); @@ -240,7 +266,9 @@ protected URL findJarForResource(URL url) throws MalformedURLException { } if (file.exists()) { - log.debug("Trying real file: " + file.getAbsolutePath()); + if (log.isDebugEnabled()) { + log.debug("Trying real file: " + file.getAbsolutePath()); + } testUrl = file.toURI().toURL(); if (isJar(testUrl)) { return testUrl; @@ -251,7 +279,9 @@ protected URL findJarForResource(URL url) throws MalformedURLException { log.warn("Invalid JAR URL: " + jarUrl); } - log.debug("Not a JAR: " + jarUrl); + if (log.isDebugEnabled()) { + log.debug("Not a JAR: " + jarUrl); + } return null; } @@ -288,7 +318,9 @@ protected boolean isJar(URL url, byte[] buffer) { is = url.openStream(); is.read(buffer, 0, JAR_MAGIC.length); if (Arrays.equals(buffer, JAR_MAGIC)) { - log.debug("Found JAR: " + url); + if (log.isDebugEnabled()) { + log.debug("Found JAR: " + url); + } return true; } } catch (Exception e) { diff --git a/src/main/java/org/apache/ibatis/io/ResolverUtil.java b/src/main/java/org/apache/ibatis/io/ResolverUtil.java index 7cb98be33..b0766f88b 100644 --- a/src/main/java/org/apache/ibatis/io/ResolverUtil.java +++ b/src/main/java/org/apache/ibatis/io/ResolverUtil.java @@ -247,7 +247,9 @@ protected void addIfMatching(Test test, String fqn) { try { String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.'); ClassLoader loader = getClassLoader(); - log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]"); + if (log.isDebugEnabled()) { + log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]"); + } Class type = loader.loadClass(externalName); if (test.matches(type)) { diff --git a/src/main/java/org/apache/ibatis/io/VFS.java b/src/main/java/org/apache/ibatis/io/VFS.java index 45d1b08b8..06187d3af 100644 --- a/src/main/java/org/apache/ibatis/io/VFS.java +++ b/src/main/java/org/apache/ibatis/io/VFS.java @@ -65,8 +65,10 @@ public static VFS getInstance() { try { vfs = impl.newInstance(); if (vfs == null || !vfs.isValid()) { - log.debug("VFS implementation " + impl.getName() + + if (log.isDebugEnabled()) { + log.debug("VFS implementation " + impl.getName() + " is not valid in this environment."); + } } } catch (InstantiationException e) { log.error("Failed to instantiate " + impl, e); @@ -77,7 +79,9 @@ public static VFS getInstance() { } } - log.debug("Using VFS adapter " + vfs.getClass().getName()); + if (log.isDebugEnabled()) { + log.debug("Using VFS adapter " + vfs.getClass().getName()); + } return VFS.instance = vfs; } @@ -98,7 +102,9 @@ protected static Class getClass(String className) { return Thread.currentThread().getContextClassLoader().loadClass(className); // return ReflectUtil.findClass(className); } catch (ClassNotFoundException e) { - log.debug("Class not found: " + className); + if (log.isDebugEnabled()) { + log.debug("Class not found: " + className); + } return null; } } diff --git a/src/main/java/org/apache/ibatis/logging/LogFactory.java b/src/main/java/org/apache/ibatis/logging/LogFactory.java index c9c2b419b..ceac359e5 100644 --- a/src/main/java/org/apache/ibatis/logging/LogFactory.java +++ b/src/main/java/org/apache/ibatis/logging/LogFactory.java @@ -125,7 +125,9 @@ private static void setImplementation(Class implClass) { try { Constructor candidate = implClass.getConstructor(new Class[] { String.class }); Log log = candidate.newInstance(new Object[] { LogFactory.class.getName() }); - log.debug("Logging initialized using '" + implClass + "' adapter."); + if (log.isDebugEnabled()) { + log.debug("Logging initialized using '" + implClass + "' adapter."); + } logConstructor = candidate; } catch (Throwable t) { throw new LogException("Error setting Log implementation. Cause: " + t, t); diff --git a/src/main/java/org/apache/ibatis/logging/log4j2/Log4j2AbstractLoggerImpl.java b/src/main/java/org/apache/ibatis/logging/log4j2/Log4j2AbstractLoggerImpl.java index 2304fbbef..50456895f 100644 --- a/src/main/java/org/apache/ibatis/logging/log4j2/Log4j2AbstractLoggerImpl.java +++ b/src/main/java/org/apache/ibatis/logging/log4j2/Log4j2AbstractLoggerImpl.java @@ -22,49 +22,56 @@ import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.spi.AbstractLogger; -import org.apache.logging.log4j.spi.AbstractLoggerWrapper; +import org.apache.logging.log4j.spi.ExtendedLoggerWrapper; /** * @author Eduardo Macarron */ public class Log4j2AbstractLoggerImpl implements Log { - + private static Marker MARKER = MarkerManager.getMarker(LogFactory.MARKER); - + private static final String FQCN = Log4j2Impl.class.getName(); - - private AbstractLoggerWrapper log; + + private ExtendedLoggerWrapper log; public Log4j2AbstractLoggerImpl(AbstractLogger abstractLogger) { - log = new AbstractLoggerWrapper(abstractLogger, abstractLogger.getName(), abstractLogger.getMessageFactory()); + log = new ExtendedLoggerWrapper(abstractLogger, abstractLogger.getName(), abstractLogger.getMessageFactory()); } + @Override public boolean isDebugEnabled() { return log.isDebugEnabled(); } + @Override public boolean isTraceEnabled() { return log.isTraceEnabled(); } + @Override public void error(String s, Throwable e) { - log.log(MARKER, FQCN, Level.ERROR, new SimpleMessage(s), e); + log.logIfEnabled(FQCN, Level.ERROR, MARKER, new SimpleMessage(s), e); } + @Override public void error(String s) { - log.log(MARKER, FQCN, Level.ERROR, new SimpleMessage(s), null); + log.logIfEnabled(FQCN, Level.ERROR, MARKER, new SimpleMessage(s), null); } + @Override public void debug(String s) { - log.log(MARKER, FQCN, Level.DEBUG, new SimpleMessage(s), null); + log.logIfEnabled(FQCN, Level.DEBUG, MARKER, new SimpleMessage(s), null); } + @Override public void trace(String s) { - log.log(MARKER, FQCN, Level.TRACE, new SimpleMessage(s), null); + log.logIfEnabled(FQCN, Level.TRACE, MARKER, new SimpleMessage(s), null); } + @Override public void warn(String s) { - log.log(MARKER, FQCN, Level.WARN, new SimpleMessage(s), null); + log.logIfEnabled(FQCN, Level.WARN, MARKER, new SimpleMessage(s), null); } } diff --git a/src/main/java/org/apache/ibatis/mapping/SqlCommandType.java b/src/main/java/org/apache/ibatis/mapping/SqlCommandType.java index f8902d238..af27379bb 100644 --- a/src/main/java/org/apache/ibatis/mapping/SqlCommandType.java +++ b/src/main/java/org/apache/ibatis/mapping/SqlCommandType.java @@ -19,5 +19,5 @@ * @author Clinton Begin */ public enum SqlCommandType { - UNKNOWN, INSERT, UPDATE, DELETE, SELECT; + UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH; } diff --git a/src/main/java/org/apache/ibatis/parsing/GenericTokenParser.java b/src/main/java/org/apache/ibatis/parsing/GenericTokenParser.java index 71eb1ad22..0d6c3e7ac 100644 --- a/src/main/java/org/apache/ibatis/parsing/GenericTokenParser.java +++ b/src/main/java/org/apache/ibatis/parsing/GenericTokenParser.java @@ -39,7 +39,7 @@ public String parse(String text) { while (start > -1) { if (start > 0 && src[start - 1] == '\\') { // the variable is escaped. remove the backslash. - builder.append(src, offset, start - 1).append(openToken); + builder.append(src, offset, start - offset - 1).append(openToken); offset = start + openToken.length(); } else { int end = text.indexOf(closeToken, start); diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java index 89f27e94a..91d44088c 100644 --- a/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java +++ b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java @@ -114,7 +114,7 @@ public Object getProperty(Map context, Object target, Object name) Map map = (Map) target; Object result = map.get(name); - if (result != null) { + if (map.containsKey(name) || result != null) { return result; } diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/ForEachSqlNode.java b/src/main/java/org/apache/ibatis/scripting/xmltags/ForEachSqlNode.java index c9c1625ae..f6708562b 100644 --- a/src/main/java/org/apache/ibatis/scripting/xmltags/ForEachSqlNode.java +++ b/src/main/java/org/apache/ibatis/scripting/xmltags/ForEachSqlNode.java @@ -52,6 +52,9 @@ public ForEachSqlNode(Configuration configuration, SqlNode contents, String coll public boolean apply(DynamicContext context) { Map bindings = context.getBindings(); final Iterable iterable = evaluator.evaluateIterable(collectionExpression, bindings); + if (!iterable.iterator().hasNext()) { + return true; + } boolean first = true; applyOpen(context); int i = 0; diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/OgnlCache.java b/src/main/java/org/apache/ibatis/scripting/xmltags/OgnlCache.java index 2bb1ff6bb..813a7ad78 100644 --- a/src/main/java/org/apache/ibatis/scripting/xmltags/OgnlCache.java +++ b/src/main/java/org/apache/ibatis/scripting/xmltags/OgnlCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2012 the original author or authors. + * Copyright 2009-2014 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,54 +16,45 @@ package org.apache.ibatis.scripting.xmltags; -import java.io.StringReader; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import ognl.ExpressionSyntaxException; -import ognl.Node; import ognl.Ognl; import ognl.OgnlException; -import ognl.OgnlParser; -import ognl.ParseException; -import ognl.TokenMgrError; import org.apache.ibatis.builder.BuilderException; /** - * - * Caches OGNL parsed expressions. Have a look at - * http://code.google.com/p/mybatis/issues/detail?id=342 - * - */ -/** - * @author Clinton Begin + * Caches OGNL parsed expressions. + * + * @see http://code.google.com/p/mybatis/issues/detail?id=342 + * + * @author Eduardo Macarron */ -public class OgnlCache { +public final class OgnlCache { - private static final Map expressionCache = new ConcurrentHashMap(); + private static final Map expressionCache = new ConcurrentHashMap(); + + private OgnlCache() { + // Prevent Instantiation of Static Class + } public static Object getValue(String expression, Object root) { try { - return Ognl.getValue(parseExpression(expression), root); + Map context = Ognl.createDefaultContext(root, new OgnlClassResolver()); + return Ognl.getValue(parseExpression(expression), context, root); } catch (OgnlException e) { throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e); } } private static Object parseExpression(String expression) throws OgnlException { - try { - Node node = expressionCache.get(expression); - if (node == null) { - node = new OgnlParser(new StringReader(expression)).topLevelExpression(); - expressionCache.put(expression, node); - } - return node; - } catch (ParseException e) { - throw new ExpressionSyntaxException(expression, e); - } catch (TokenMgrError e) { - throw new ExpressionSyntaxException(expression, e); + Object node = expressionCache.get(expression); + if (node == null) { + node = Ognl.parseExpression(expression); + expressionCache.put(expression, node); } + return node; } } diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/OgnlClassResolver.java b/src/main/java/org/apache/ibatis/scripting/xmltags/OgnlClassResolver.java new file mode 100644 index 000000000..1fa2cf746 --- /dev/null +++ b/src/main/java/org/apache/ibatis/scripting/xmltags/OgnlClassResolver.java @@ -0,0 +1,57 @@ +/* + * Copyright 2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ibatis.scripting.xmltags; + +import java.util.HashMap; +import java.util.Map; + +import ognl.ClassResolver; + +import org.apache.ibatis.io.Resources; + +/** + * Custom ognl {@code ClassResolver} which behaves same like ognl's + * {@code DefaultClassResolver}. But uses the {@code Resources} + * utility class to find the target class instead of {@code Class#forName(String)}. + * + * @see https://github.com/mybatis/mybatis-3/issues/161 + * + * @author Daniel Guggi + * + */ +public class OgnlClassResolver implements ClassResolver { + + private Map> classes = new HashMap>(101); + + @Override + public Class classForName(String className, Map context) throws ClassNotFoundException { + Class result = null; + if ((result = classes.get(className)) == null) { + try { + result = Resources.classForName(className); + } catch (ClassNotFoundException e1) { + if (className.indexOf('.') == -1) { + result = Resources.classForName("java.lang." + className); + classes.put("java.lang." + className, result); + } + } + classes.put(className, result); + } + return result; + } + +} diff --git a/src/main/java/org/apache/ibatis/session/Configuration.java b/src/main/java/org/apache/ibatis/session/Configuration.java index fdf87f964..e74800f28 100644 --- a/src/main/java/org/apache/ibatis/session/Configuration.java +++ b/src/main/java/org/apache/ibatis/session/Configuration.java @@ -109,6 +109,7 @@ public class Configuration { protected JdbcType jdbcTypeForNull = JdbcType.OTHER; protected Set lazyLoadTriggerMethods = new HashSet(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" })); protected Integer defaultStatementTimeout; + protected Integer defaultFetchSize; protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE; protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL; @@ -362,6 +363,14 @@ public void setDefaultStatementTimeout(Integer defaultStatementTimeout) { this.defaultStatementTimeout = defaultStatementTimeout; } + public Integer getDefaultFetchSize() { + return defaultFetchSize; + } + + public void setDefaultFetchSize(Integer defaultFetchSize) { + this.defaultFetchSize = defaultFetchSize; + } + public boolean isUseColumnLabel() { return useColumnLabel; } diff --git a/src/main/java/org/apache/ibatis/transaction/jdbc/JdbcTransaction.java b/src/main/java/org/apache/ibatis/transaction/jdbc/JdbcTransaction.java index 6848c6db7..8f715d1ff 100644 --- a/src/main/java/org/apache/ibatis/transaction/jdbc/JdbcTransaction.java +++ b/src/main/java/org/apache/ibatis/transaction/jdbc/JdbcTransaction.java @@ -122,8 +122,10 @@ protected void resetAutoCommit() { connection.setAutoCommit(true); } } catch (SQLException e) { - log.debug("Error resetting autocommit to true " + if (log.isDebugEnabled()) { + log.debug("Error resetting autocommit to true " + "before closing the connection. Cause: " + e); + } } } diff --git a/src/site/es/xdoc/configuration.xml b/src/site/es/xdoc/configuration.xml index 2d3320f7a..acb713ff5 100644 --- a/src/site/es/xdoc/configuration.xml +++ b/src/site/es/xdoc/configuration.xml @@ -131,7 +131,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, true | false - true + false diff --git a/src/site/es/xdoc/logging.xml b/src/site/es/xdoc/logging.xml index 5a1565445..00bd314a8 100644 --- a/src/site/es/xdoc/logging.xml +++ b/src/site/es/xdoc/logging.xml @@ -90,18 +90,18 @@ org.apache.ibatis.logging.LogFactory.useStdOutLogging();]]> JDK Logging API - +

Para ver el log de las sentencias debes activar el log en un paquete, el nombre plenamente cualificado de una clase, un namespace o un nombre plenamente cualificado de un mapped statement.

-

Nuevamente, como hagas esto es dependiente de la implementación de logging que se esté usando. Mostraremos cómo hacerlo con Log4j. Configurar los servicios de logging es simplemente cuestión de añadir uno o varios ficheros de configuración (por ejemplo log4j.properties) y a veces un nuevo JAR (por ejemplo log4j.jar). El ejemplo siguiente configura todos los servicios de logging para que usen log4j como proveedor. Sólo son dos pasos: +

Nuevamente, cómo hagas esto es dependiente de la implementación de logging que se esté usando. Mostraremos cómo hacerlo con Log4j. Configurar los servicios de logging es simplemente cuestión de añadir uno o varios ficheros de configuración (por ejemplo log4j.properties) y a veces un nuevo JAR (por ejemplo log4j.jar). El ejemplo siguiente configura todos los servicios de logging para que usen log4j como proveedor. Sólo son dos pasos:

-

Paso 1: Añade el fichero Log4J JAR +

Paso 1: Añade el fichero Log4j JAR

Dado que usamos Log4j, necesitaremos asegurarnos que el fichero JAR está disponible para nuestra aplicación. Para usar Log4j, necesitas añadir el fichero JAR al classpath de tu aplicación. Puedes descargar Log4j desde la URL indicada más arriba.

En aplicaciones Web o de empresa debes añadir tu fichero log4j.java a tu directorio WEB-INF/lib, y en una aplicación standalone simplemente añádela al parámetro –classpath de la JVM.

- Paso 2: Configurar Log4J + Paso 2: Configurar Log4j

Configurar Log4j es sencillo. Supongamos que quieres habilitar el log para este mapper:

- true + false diff --git a/src/site/ja/xdoc/statement-builders.xml b/src/site/ja/xdoc/statement-builders.xml index 074bfb6f2..c8a846607 100644 --- a/src/site/ja/xdoc/statement-builders.xml +++ b/src/site/ja/xdoc/statement-builders.xml @@ -100,8 +100,8 @@ public String deletePersonSql() { // メソッドチェーン(Builder / Fluent スタイル) public String insertPersonSql() { String sql = new SQL() - .INSERT_INTO("PERSON"); - .VALUES("ID, FIRST_NAME", "${id}, ${firstName}"); + .INSERT_INTO("PERSON") + .VALUES("ID, FIRST_NAME", "${id}, ${firstName}") .VALUES("LAST_NAME", "${lastName}") .toString(); return sql; diff --git a/src/site/ko/xdoc/configuration.xml b/src/site/ko/xdoc/configuration.xml index c2b48c7c7..a722d2d0d 100644 --- a/src/site/ko/xdoc/configuration.xml +++ b/src/site/ko/xdoc/configuration.xml @@ -115,14 +115,14 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, lazyLoadingEnabled - 늦은 로딩을 사용할지에 대한 여부. 사용하지 않는다면 모두 즉시 로딩할 것이다. - This value can be superseded for an specific relation by using the fetchType attribute on it. + 늦은 로딩을 사용할지에 대한 여부. 사용하지 않는다면 모두 즉시 로딩할 것이다. + 이 값은 fetchType 속성을 사용해서 대체할 수 있다. true | false - true + false @@ -242,9 +242,9 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, localCacheScope - MyBatis uses local cache to prevent circular references and speed up repeated nested queries. - By default (SESSION) all queries executed during a session are cached. If localCacheScope=STATEMENT local session will be used just for - statement execution, no data will be shared between two different calls to the same SqlSession. + 마이바티스는 순환참조를 막거나 반복된 쿼리의 속도를 높히기 위해 로컬캐시를 사용한다. + 디폴트 설정인 SESSION을 사용해서 동일 세션의 모든 쿼리를 캐시한다. + localCacheScope=STATEMENT 로 설정하면 로컬 세션은 구문 실행할때만 사용하고 같은 SqlSession에서 두개의 다른 호출사이에는 데이터를 공유하지 않는다. SESSION | STATEMENT @@ -258,11 +258,11 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, jdbcTypeForNull - Specifies the JDBC type for null values when no specific JDBC type was provided for the parameter. - Some drivers require specifying the column JDBC type but others work with generic values like NULL, VARCHAR or OTHER. + JDBC타입을 파라미터에 제공하지 않을때 null값을 처리한 JDBC타입을 명시한다. + 일부 드라이버는 칼럼의 JDBC타입을 정의하도록 요구하지만 대부분은 NULL, VARCHAR 나 OTHER 처럼 일반적인 값을 사용해서 동작한다. - JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER + JdbcType 이늄. 대부분은 NULL, VARCHAR 나 OTHER 를 공통적으로 사용한다. OTHER @@ -273,10 +273,10 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, lazyLoadTriggerMethods - Specifies which Object's methods trigger a lazy load + 늦은 로딩을 야기하는 객체의 메소드를 명시 - A method name list separated by commas + 메소드 이름을 나열하고 여러개일 경우 콤마(,) 로 구분 equals,clone,hashCode,toString @@ -287,10 +287,10 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, defaultScriptingLanguage - Specifies the language used by default for dynamic SQL generation. + 동적으로 SQL을 만들기 위해 기본적으로 사용하는 언어를 명시 - A type alias or fully qualified class name. + 타입별칭이나 패키지 경로를 포함한 클래스명 org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver @@ -301,7 +301,9 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, callSettersOnNulls - Specifies if setters or map's put method will be called when a retrieved value is null. It is useful when you rely on Map.keySet() or null value initialization. Note primitives such as (int,boolean,etc.) will not be set to null. + 가져온 값이 null일때 setter나 맵의 put 메소드를 호출할지를 명시 + Map.keySet() 이나 null값을 초기화할때 유용하다. + int, boolean 등과 같은 원시타입은 null을 셋팅할 수 없다는 점은 알아두면 좋다. true | false @@ -315,13 +317,13 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, logPrefix - Specifies the prefix string that MyBatis will add to the logger names. + 마이바티스가 로거(logger) 이름에 추가할 접두사 문자열을 명시 - Any String + 문자열 - Not set + 셋팅하지 않음 @@ -329,13 +331,14 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, logImpl - Specifies which logging implementation MyBatis should use. If this setting is not present logging implementation will be autodiscovered. + 마이바티스가 사용할 로깅 구현체를 명시 + 이 설정을 사용하지 않으면 마이바티스가 사용할 로깅 구현체를 자동으로 찾는다. SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING - Not set + 셋팅하지 않음 @@ -343,7 +346,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, proxyFactory - Specifies the proxy tool that MyBatis will use for creating lazy loading capable objects. + 마이바티스가 늦은 로딩을 처리할 객체를 생성할 때 사용할 프록시 툴을 명시 CGLIB | JAVASSIST @@ -945,9 +948,8 @@ public class ExampleTypeHandler extends BaseTypeHandler {

JDBC타입에 대한 자동검색 기능은 애노테이션을 명시한 경우에만 가능하다는 것을 알아둘 필요가 있다.

- You can create a generic TypeHandler that is able to handle more than one class. For that purpose - add a constructor that receives the class as a parameter and MyBatis will pass the actual class when - constructing the TypeHandler. + 한개 이상의 클래스를 다루는 제네릭 TypeHandler를 만들수 있다. + 파라미터로 클래스를 가져오는 생성자를 추가하고 마이바티스는 TypeHandler를 만들때 실제 클래스를 전달할 것이다.

extends BaseTypeHandler { ... ]]> -

EnumTypeHandler and EnumOrdinalTypeHandler are generic TypeHandlers. We will learn - about them in the following section. +

EnumTypeHandlerEnumOrdinalTypeHandler 는 제네릭 TypeHandler이다. + 이어서 각각을 다룬다.

- If you want to map an Enum, you'll need to use either - EnumTypeHandler or EnumOrdinalTypeHandler. + Enum을 매핑하고자 한다면 EnumTypeHandlerEnumOrdinalTypeHandler 를 사용할 필요가 있을것이다.

-

For example, let's say that we need to store the rounding mode that - should be used with some number if it needs to be rounded. By default, MyBatis - uses EnumTypeHandler to convert the Enum - values to their names. +

예를들어, 순환 방식으로 몇개의 숫자를 사용하는 순환모드를 저장할 필요가 있다고 해보자. + 기본적으로 마이바티스는 Enum 값을 각각의 이름으로 변환하기 위해 EnumTypeHandler 를 사용한다.

- Note EnumTypeHandler is special in the sense that unlike other handlers, - it does not handle just one specific class, but any class that extends Enum + EnumTypeHandler는 특히 다른 핸들러와 차이가 있다. + 어떤 하나의 특정 클래스를 다루지 않고 Enum 을 확장하는 모든 클래스를 다룬다. -

However, we may not want to store names. Our DBA may insist on an - integer code instead. That's just as easy: add EnumOrdinalTypeHandler - to the typeHandlers in your config file, and now each - RoundingMode will be mapped to an integer using its ordinal value. +

아무리 이름을 저장하려해도 DBA는 숫자코드를 고집할수 있다. 이름대신 숫자코드를 저장하는 방법은 쉽다. + 설정파일의 typeHandlersEnumOrdinalTypeHandler 를 추가하자. + 그러면 각각의 RoundingMode는 순서값을 사용해서 숫자를 매핑할 것이다.

@@ -994,19 +992,15 @@ public class GenericTypeHandler extends BaseTypeHandler { ]]>

- But what if you want to map the same Enum to a - string in one place and to integer in another? + 같은 Enum을 사용해서 어떤곳에는 문자열로 매핑하고 다른곳에는 숫자로 매핑해야 한다면 무엇을 해야 하나?

- The auto-mapper will automatically use EnumOrdinalTypeHandler, - so if we want to go back to using plain old ordinary - EnumTypeHandler, we have to tell it, by explicitly setting - the type handler to use for those SQL statements. + 자동매퍼는 EnumOrdinalTypeHandler 를 자동으로 사용할 것이다. + 그래서 평범한 순서를 나타내는 EnumTypeHandler 를 사용하고자 한다면 SQL구문에 사용할 타입핸들러를 몀시적으로 설정한다.

- (Mapper files aren't covered until the next section, so if this is your first - time reading through the documentation, you may want to skip this for now - and come back to it later.) + (매퍼 파일은 다음절까지는 다루지 않는다. + 그래서 문서를 보면서 처음 봤다면 일단 이부분은 건너띄고 다음에 다시 볼수도 있다. )

extends BaseTypeHandler { ]]>

- Note that this forces us to use a resultMap - instead of a resultType in our select statements. + 여기서 사용한 select구문에서는 resultType 대신에 resultMap을 사용해야 한다는 점을 알아두자.

@@ -1296,7 +1289,7 @@ data_source 프로퍼티가 InitialContext 에서 직접 찾을 것이다.

이 설정은 인스턴스화할 때 InitialContext 생성자에 “encoding” 프로퍼티를 “UTF8” 로 전달한다.

- You can plug any 3rd party DataSource by implementing the interface org.apache.ibatis.datasource.DataSourceFactory: + org.apache.ibatis.datasource.DataSourceFactory 인터페이스를 구현해서 또다른 DataSource구현체를 만들수 있다.

}]]>

- org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory can be used as super class - class to build new datasource adapters. For example this is the code needed to plug C3P0: + org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory 는 새로운 데이터소스를 만들기 위한 상위 클래스처럼 사용할 수 있다. + 예를들면 다음의 코드를 사용해서 C3P0를 사용할 수 있다.

-

To set it up, add a property for each setter method you want MyBatis to call. - Below depicts a sample configuration which connects to a PostgreSQL database:

+

마이바티스가 호출할 setter메소드가 사용하는 프로퍼티를 추가해서 설정한다. + 다음의 설정은 PostgreSQL 데이터베이스에 연결할때 사용한 샘플 설정이다.

diff --git a/src/site/ko/xdoc/dynamic-sql.xml b/src/site/ko/xdoc/dynamic-sql.xml index 89682a1fc..abcf869d2 100644 --- a/src/site/ko/xdoc/dynamic-sql.xml +++ b/src/site/ko/xdoc/dynamic-sql.xml @@ -179,7 +179,7 @@ AND title like ‘someTitle’]]> 살펴볼 것이다.

-

The bind element lets you create a variable out of an OGNL expression and bind it to the context. For example:

+

bind 엘리먼트는 OGNL표현을 사용해서 변수를 만든 뒤 컨텍스트에 바인딩한다. 예를들면

@@ -203,31 +203,31 @@ AND title like ‘someTitle’]]> ]]>
-

Starting from version 3.2 MyBatis supports pluggable scripting languages, - so you can plug a language driver and use that language to write your dynamic - SQL queries.

-

There are two built-in languages:

+

마이바티스 3.2부터는 플러그인 형태로 스크립트 언어를 사용할 수 있다. + 그래서 언어 드라이버를 장착하고 동적 SQL쿼리를 작성할때 그 언어를 사용할 수 있다.

+

두개의 내장된 언어가 있다.

  • xml
  • raw
-

The xml language is the default one. It is able to execute all the dynamic tags we saw in the previous sections.

-

The raw language is in fact the absence of language. When using this setting MyBatis just performs the - parameter substitution and passes the statement to the database driver. As you may guess, the raw language - is faster than the xml language. +

xml 언어는 설정하지 않을때 기본으로 사용하는 값이다. + xml을 사용하면 이전에 다룬 모든 동적태그를 실행할 수 있다.

+

raw 언어는 사실 기능이 조금 부족하다. + raw설정을 사용하면 마이바티스는 파라미터를 치환해서 데이터베이스 드라이버에 구문을 전달한다. + 짐작하는 것처럼 raw 언어는 xml 언어보다 조금더 빠르다.

-

You can specify the language you want to use in an statement adding the lang attribute as follows: +

다음처럼 lang 속성을 추가해서 구문에서 사용할 언어를 명시할 수 있다.

SELECT * FROM BLOG ]]> -

Or, in the case you are using mappers, using the @Lang annotation:

+

또는 매퍼를 사용하는 경우라면 @Lang 애노테이션을 사용한다.

selectBlog(); }]]> -

You can also implement your own language driver by implementing the following interface:

+

다음의 인터페이스를 구현해서 자신만의 언어 드라이버를 구현할 수도 있다.

parameterType); diff --git a/src/site/ko/xdoc/getting-started.xml b/src/site/ko/xdoc/getting-started.xml index 85c1307ea..69d9f67ea 100644 --- a/src/site/ko/xdoc/getting-started.xml +++ b/src/site/ko/xdoc/getting-started.xml @@ -31,14 +31,14 @@

- To use the MyBatis you just need to include the + 마이바티스를 사용하기 위해 mybatis-x.x.x.jar - file in the classpath. + 파일을 클래스패스에 두어야 한다.

- If you are using Maven just add the following dependency to your pom.xml: + 메이븐을 사용한다면 pom.xml 에 다음의 설정을 추가하자.

diff --git a/src/site/ko/xdoc/index.xml b/src/site/ko/xdoc/index.xml index a290733e4..a9218241b 100644 --- a/src/site/ko/xdoc/index.xml +++ b/src/site/ko/xdoc/index.xml @@ -46,7 +46,7 @@ MyBatis 는 JDBC 코드와 수동으로 셋팅하는 파라미터와 결과 매
-

번역자 : 이동국(fromm0@gmail.com, http://ldg.pe.kr, http://me2day.net/fromm0)

+

번역자 : 이동국(fromm0@gmail.com, http://ldg.pe.kr, https://www.facebook.com/dongguk.lee.3)

diff --git a/src/site/ko/xdoc/java-api.xml b/src/site/ko/xdoc/java-api.xml index 73e846e05..5f4507d94 100644 --- a/src/site/ko/xdoc/java-api.xml +++ b/src/site/ko/xdoc/java-api.xml @@ -304,7 +304,7 @@ try { } finally { session.close(); } -

Or, If you are using jdk 1.7+ and MyBatis 3.2+, you can use the try-with-resources statement:

+

JDK 1.7이상과 마이바티스 3.2이상을 사용한다면 다음처럼 try-with-resources 구문을 사용할 수 있다.

try (SqlSession session = sqlSessionFactory.openSession()) { // following 3 lines pseudocode for "doing some work" @@ -614,13 +614,14 @@ id 를 제공하기 위해 사용된다. XML 에 정의된 결과 @ResultType Method N/A - This annotation is used when using a result handler. In that case, the return type is void - so MyBatis must have a way to determine the type of object to construct for each row. - If there is an XML result map, use the @ResultMap annotation. If the result type is - specified in XML on the <select> element, then no other annotation is - necessary. In other cases, use this annotation. For example, if a @Select annotated method - will use a result handler, the return type must be void and this annotation (or @ResultMap) - is required. This annotation is ignored unless the method return type is void. + 이 애노테이션은 결과 핸들러를 사용할때 사용한다. + 이 경우 리턴타입은 void이고 마이바티스는 각각의 레코드 정보를 가지는 객체의 타입을 결정하는 방법을 가져야만 한다. + XML 결과매핑이 있다면 @ResultMap 애노테이션을 사용하자. + 결과타입이 XML에서 <select> 엘리먼트에 명시되어 있다면 다른 애노테이션이 필요하지 않다. + 결과타입이 XML에서 <select> 엘리먼트에 명시되어 있지 않은 경우에 이 애노테이션을 사용하자. + 예를들어, @Select 애노테이션이 선언되어 있다면 메소드는 결과 핸들러를 사용할 것이다. + 결과 타입은 void여야만 하고 이 애노테이션(이나 @ResultMap)을 반드시 사용해야 한다. + 이 애노테이션은 메소드 리턴타입이 void가 아니라면 무시한다. diff --git a/src/site/ko/xdoc/sqlmap-xml.xml b/src/site/ko/xdoc/sqlmap-xml.xml index 36bd2f1e2..e528021e6 100644 --- a/src/site/ko/xdoc/sqlmap-xml.xml +++ b/src/site/ko/xdoc/sqlmap-xml.xml @@ -176,11 +176,11 @@ ps.setInt(1,id);]]> resultOrdered - This is only applicable for nested result select statements: If this is true, it - is assumed that nested results are contained or grouped together such that when a - new main result row is returned, no references to a previous result row will occur - anymore. This allows nested results to be filled much more memory friendly. Default: - false. + 이 설정은 내포된 결과를 조회하는 구문에서만 적용이 가능하다. + true로 설정하면 내포된 결과를 가져오거나 새로운 주요 결과 레코드를 리턴할때 함께 가져오도록 한다. + 이전의 결과 레코드에 대한 참조는 더 이상 발생하지 않는다. + 이 설정은 내포된 결과를 처리할때 조금 더 많은 메모리를 채운다. + 디폴트값은 false 이다. @@ -345,9 +345,8 @@ Server 와 같은 RDBMS 의 자동 증가 필드)를 받는 JDBC getGeneratedKey keyColumn - The column name(s) in the returned result set that match the properties. - Can be a comma separated list of column names if multiple generated columns - are expected. + 리턴되는 결과셋의 칼럼명은 프로퍼티에 일치한다. + 여러개의 칼럼을 사용한다면 칼럼명의 목록은 콤마를 사용해서 구분한다. @@ -683,27 +682,27 @@ public class User { - + - - + + - + - - @@ -983,8 +982,8 @@ prop1, prop2 형태로 셋팅하게 될 것이다. @@ -1044,25 +1043,27 @@ prop1, prop2 형태로 셋팅하게 될 것이다. - @@ -1126,8 +1127,8 @@ association 결과 매핑을 내포시킬수 있다. 다음은 이 방법을 사 ]]>

- What if the blog has a co-author? - The select statement would look like: + 블로그에 공동저자가 있다면 어쩌지? + select구문은 다음과 같을 것이다.

@@ -1151,7 +1152,7 @@ association 결과 매핑을 내포시킬수 있다. 다음은 이 방법을 사 ]]>

- Recall that the resultMap for Author is defined as follows. + 저자(Author)를 위한 결과매핑은 다음처럼 정의했다.

@@ -1163,8 +1164,7 @@ association 결과 매핑을 내포시킬수 있다. 다음은 이 방법을 사 ]]>

- Because the column names in the results differ from the columns defined in the resultMap, - you need to specify columnPrefix to reuse the resultMap for mapping co-author results. + 결과의 칼럼명은 결과매핑에 정의한 칼럼과는 다르기 때문에 공동저자 결과를 위한 결과매핑을 재사용하기 위해 columnPrefix 를 명시할 필요가 있다.

@@ -1365,26 +1365,27 @@ vehicle 간의 관계를 알 수 있다. 그러므로, 나머지 프로퍼티들

- As you have already seen in the previous sections, in simple cases MyBatis can auto-map the results for you - and in others you will need to build a result map. - But as you will see in this section you can also mix both strategies. - Let's have a deeper look at how auto-mapping works. + 이전의 절에서 이미 본것처럼 간단한 경우 마이바티스는 결과를 자동으로 매핑할 수 있고 간단하지 않은 경우에는 직접 결과매핑을 만들필요가 있다. + 하지만 이 절에서 보는것처럼 두가지 방법을 적절히 혼용할수도 있다. + 자동매핑을 처리하는 방법을 조금 더 보자.

- When auto-mapping results MyBatis will get the column name and look for a property with the same name ignoring case. That means that if - a column named ID and property named id are found, MyBatis will set the id property with the ID column value. + 결과를 자동매핑할때 마이바티스는 칼럼명을 가져와서 대소문자를 무시한 같은 이름의 프로퍼티를 찾을 것이다. + 칼럼명이 ID라면 id 이름의 프로퍼티를 찾는다는 것을 뜻한다. + 마이바티스는 ID 칼럼값을 사용해서 id 프로퍼티를 설정할것이다.

- Usually database columns are named using uppercase letters and underscores between words and java properties often follow the camelcase - naming covention. To enable the auto-mapping between them set the setting mapUnderscoreToCamelCase to true. + 대개 데이터베이스 칼럼은 대문자를 사용해서 명명하고 단어 사이사이에는 밑줄을 넣는다. + 자바 프로퍼티는 대개 낙나표기법을 사용해서 명명한다. + 이런 둘사이의 자동매핑을 가능하게 하기 위해서는 mapUnderscoreToCamelCase 를 true로 설정하자.

- Auto-mapping works even when there is an specific result map. When this happens, for each result map, all columns that are present in the - ResultSet that have not a manual mapping will be auto-mapped, then manual mappings will be processed. - In the following sample id and userName columns will be auto-mapped and hashed_password column will be mapped.

+ 자동매핑은 결과매핑을 선언한 경우에도 동작한다. + 이런 경우 각각의 결과매핑에서 ResultSet의 모든 칼럼은 자동매핑이 아니라 수동매핑으로 처리한다. + 다음 샘플에서 iduserName 칼럼은 자동매핑되고 hashed_password 칼럼은 수동으로 매핑한다.

select @@ -1400,25 +1401,25 @@ vehicle 간의 관계를 알 수 있다. 그러므로, 나머지 프로퍼티들 ]]>

- There are three auto-mapping levels: + 자동매핑에는 3가지가 있다.

  • - NONE - disables auto-mapping. Only manually mapped properties will be set. + NONE - 자동매핑을 사용하지 않는다. 오직 수동으로 매핑한 프로퍼티만을 설정할것이다.
  • - PARTIAL - will auto-map results except those that have nested result mappings defined inside (joins). + PARTIAL - 조인 내부에 정의한 내포된 결과매핑을 제외하고 자동매핑한다.
  • - FULL - auto-maps everything. + FULL - 모든것을 자동매핑한다.

- The default value is PARTIAL, and it is so for a reason. When FULL is used auto-mapping will - be performed when processing join results and joins retrieve data of several different entities in the same row - hence this may result in undesired mappings. To understand the risk have a look at the following sample: + 디폴트값은 PARTIAL이다. + FULL 을 사용할때 자동매핑은 조인결과나 같은 레코드의 여러가지 다른 엔터티를 가져올때 예상치 못한 문제를 야기할 수 있다. + 이런 위험을 이해하기 위해 다음의 샘플을 보자.

@@ -1439,15 +1440,14 @@ vehicle 간의 관계를 알 수 있다. 그러므로, 나머지 프로퍼티들 ]]>

- With this result map both Blog and Author will be auto-mapped. But note that Author has an id - property and there is a column named id in the ResultSet so Author's id will be filled with Blog's id, and that is not - what you were expecting. So use the FULL option with caution. + BlogAuthor 는 자동매핑으로 처리하지만 Authorid 프로퍼티를 가지고 ResultSet은 id 칼럼을 가진다. + 그래서 기대한 것과는 달리 저자(Author)의 id는 블로그(Blog)의 id로 채워질것이다. + FULL 는 이런 위험을 가진다.

- Regardless of the auto-mapping level configured you can enable or disable the automapping for an specific statement - by adding the attribute autoMapping to it: + 자동매핑 설정에 상관없이 구문별로 autoMapping속성을 추가해서 자동매핑을 사용하거나 사용하지 않을수도 있다.

diff --git a/src/site/ko/xdoc/statement-builders.xml b/src/site/ko/xdoc/statement-builders.xml index 4d7ccb2d4..84ab4840f 100644 --- a/src/site/ko/xdoc/statement-builders.xml +++ b/src/site/ko/xdoc/statement-builders.xml @@ -21,13 +21,18 @@ xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd"> - MyBatis 3 | 정책 빌더 + MyBatis 3 | 구문 빌더 Clinton Begin 이동국(한국어 번역) -
+ +
+ +

This section is outdated in the Korean version. Please refer to the English manual. Any help with the Korean translation will be really welcome.

+
+

자바 개발자에게 가장 끔찍한 일중 하나는 자바 코드에서 내장 SQL 을 처리하는 것이다. SQL 은 항상 동적으로 생성되기 때문에 그렇다. 반면에 파일이나 저장 프로시저를 외부에 둘수도 있다. 이미 그렇게 하고 있다면, MyBatis 는 XML 매핑에서 diff --git a/src/site/site_zh.xml b/src/site/site_zh.xml index 3a6ac8fc8..d6616f6ce 100644 --- a/src/site/site_zh.xml +++ b/src/site/site_zh.xml @@ -14,18 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - + -

- + + - + @@ -36,12 +33,12 @@ - + - + @@ -49,11 +46,8 @@ - - - - - + + diff --git a/src/site/xdoc/configuration.xml b/src/site/xdoc/configuration.xml index f93995ae5..f26ea059a 100644 --- a/src/site/xdoc/configuration.xml +++ b/src/site/xdoc/configuration.xml @@ -176,7 +176,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, true | false
@@ -1691,7 +1691,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader,properties);]]

org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory can be used as super class - class to build new datasource adapters. For example this is the code needed to plug C3P0: + to build new datasource adapters. For example this is the code needed to plug C3P0:

- MyBatis 3 | XML 映射配置文件 + MyBatis 3 | 配置 Clinton Begin Nan Lei + Dongxu Wang
-

-MyBatis 的 XML 配置文件包含了影响 MyBatis 行为甚深的设置和属性信息。 -XML 文档 -的高层级结构如下: -

+

MyBatis 的配置文件包含了影响 MyBatis 行为甚深的设置(settings)和属性(properties)信息。文档的顶层结构如下:

  • @@ -55,24 +52,18 @@ XML 文档
-
  • databaseIdProvider chinese?
  • +
  • databaseIdProvider 数据库厂商标识
  • mappers 映射器
  • -

    -这些是外部化的, -可替代的属性, -这些属性也可以配置在典型的 Java 属性配置文件中, -或者通过 properties 元素的子元素来传递。例如: -

    +

    这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递。例如:

    ]]> -

    -其中的属性就可以在整个配置文件中使用,使用可替换的属性来实现动态配置。比如: +

    其中的属性就可以在整个配置文件中使用来替换需要动态配置的属性值。比如:

    @@ -80,49 +71,27 @@ XML 文档 ]]> -

    -这个例子中的 username 和 password 将会由 properties 元素中设置的值来替换。 -driver 和 -url 属性将会从包含进来的 config.properties 文件中的值来替换。这里提供很多配置的选项。 -

    -

    -属性也可以被传递到 SqlSessionBuilder.build()方法中。例如: -

    +

    这个例子中的 username 和 password 将会由 properties 元素中设置的相应值来替换。driver 和 url 属性将会由 config.properties 文件中对应的值来替换。这样就为配置提供了诸多灵活选择。

    +

    属性也可以被传递到 SqlSessionBuilder.build()方法中。例如:

    -

    -如果在这些地方,属性多于一个的话,MyBatis 按照如下的顺序加载它们: -

    +

    如果属性在不只一个地方进行了配置,那么 MyBatis 将按照下面的顺序来加载:

    • 在 properties 元素体内指定的属性首先被读取。
    • -
    • 从类路径下资源或 properties 元素的 url 属性中加载的属性第二被读取,它会 -覆盖已经存在的完全一样的属性。 +
    • 然后会读取从类路径下资源或 properties 元素中的 url 属性(url attributes)中加载的属性,它会覆盖已读取的同名属性。
    • -
    • 作为方法参数传递的属性最后被读取, -它也会覆盖任一已经存在的完全一样的 -属性,这些属性可能是从 properties 元素体内和资源/url 属性中加载的。 +
    • 最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。
    -

    -因此, -最高优先级的属性是那些作为方法参数的, -然后是资源/url 属性, -最后是 properties -元素中指定的属性。 -

    +

    因此,通过方法参数传递的属性具有最高优先级,资源文件及 url 属性配置的次之,最低优先级的是 properties 属性中指定的属性。

    -

    -这些是极其重要的调整, -它们会修改 MyBatis 在运行时的行为方式。 -下面这个表格描述 -了设置信息,它们的含义和默认值。 -

    +

    调整 settings 中的设置是非常关键的,它们会改变 MyBatis 的运行时行为。下表描述了设置中各项的意图、默认值等。

    ResultMap AttributesResultMap 속성
    AttributeDescription속성설명
    idA unique identifier in this namespace that can be used to reference this result map.결과매핑을 참조하기 위해 사용할 수 있는 값으로 네임스페이스에서 유일한 식별자
    typeA fully qualified Java class name, or a type alias (see the table above for the list of built-in type aliases). + 패키지를 포함한 자바 클래스명이나 타입별칭(내장된 타입별칭이 목록은 위 표를 보자.).
    autoMappingIf present, MyBatis will enable or disable the automapping for this ResultMap. - This attribute overrides the global autoMappingBehavior. Default: unset. + 이 설정을 사용하면 마이바티스는 결과매핑을 자동매핑으로 처리할지 말지를 처리한다. + 이 속성은 autoMappingBehavior 라는 전역설정을 덮는다. 디폴트는 unset이다.
    fetchType - Optional. Valid values are lazy and eager. If present, it supersedes - the global configuration parameter lazyLoadingEnabled for this mapping. + 선택가능한 속성으로 사용가능한 값은 lazyeager이다. + 이 속성을 사용하면 전역 설정파라미터인 lazyLoadingEnabled 를 대체한다.
    columnPrefix - When joining multiple tables, you would have to use column alias to avoid duplicated - column names in the ResultSet. Specifying columnPrefix allows you to map such columns - to an external resultMap. Please see the example explained later in this section. + 여러개의 테이블을 조인할때 ResultSet에서 칼럼명의 중복을 피하기 위해 칼럼별칭을 사용할 수 있다. + 칼럼을 외부 결과매핑에 매핑하기 위해 columnPrefix를 명시하자. 이 절의 뒤에 나오는 에제를 보자.
    notNullColumn - By default a child object is created only if at least one of the columns mapped to the child's properties - is non null. With this attribute you can change this behaviour by specifiying which columns must have a value - so MyBatis will create a child object only if any of those columns is not null. Multiple column names can be - specified using a comma as a separator. Default value: unset. + 기본적으로 자식객체는 칼럼중 적어도 하나를 null이 아닌 자식객체의 프로퍼티에 매핑할때 만들어진다. + 이 속성을 사용해서 칼럼이 값을 가져야만 하는 것을 명시해서 행위를 변경할 수 있다. + 그래서 마이바티스는 이러한 칼럼이 null이 아닐때만 자식 객체를 만들것이다. + 여러개의 칼럼명은 구분자로 콤마를 사용해서 명시한다. + 디폴트값은 unset이다.
    autoMappingIf present, MyBatis will enable or disable automapping when mapping the result to this property. - This attribute overrides the global autoMappingBehavior. - Note that it has no effect on an external resultMap, so it is pointless to use it with select or resultMap attribute. Default value: unset. + 이 속성을 사용하면 마이바티스는 결과를 프로퍼티에 매핑할때 자동매핑을 사용할지 말지를 정한다. + 이 속성은 전역설정인 autoMappingBehavior를 무시하게 한다. + 외부 결과매핑에는 영향을 주지 않는다. + 그래서 selectresultMap 속성을 함께 사용하는 것은 의미가 없다. + 디폴트값은 unset이다.
    - true + false
    @@ -139,7 +108,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, cacheEnabled @@ -168,9 +136,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, aggressiveLazyLoading -
    - 这个配置使全局的映射器启用或禁用 缓存。 + 该配置影响的所有映射器中配置的缓存的全局开关。 true | false @@ -153,14 +122,13 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, lazyLoadingEnabled - 全局启用或禁用延迟加载。当禁用时, 所有关联对象都会即时加载。 - This value can be superseded for an specific relation by using the fetchType attribute on it. + 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 true | false - true + false
    - 当启用时, 有延迟加载属性的对象在被 - 调用时将会完全加载任意属性。否则, - 每种属性将会按需要加载。 + 当启用时,带有延迟加载属性的对象的加载与否完全取决于对任意延迟属性的调用;反之,每种属性将会按需加载。 true | false @@ -184,8 +150,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, multipleResultSetsEnabled - 允许或不允许多种结果集从一个单独 - 的语句中返回(需要适合的驱动) + 是否允许单一语句返回多结果集(需要兼容驱动)。 true | false @@ -199,11 +164,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, useColumnLabel - 使用列标签代替列名。 - 不同的驱动在这 - 方便表现不同。 - 参考驱动文档或充分测 - 试两种方法来决定所使用的驱动。 + 使用列标签代替列名。不同的驱动在这方面会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 true | false @@ -217,13 +178,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, useGeneratedKeys - 允许 JDBC 支持生成的键。 - 需要适合的 - 驱动。 - 如果设置为 true 则这个设置强制 - 生成的键被使用, - 尽管一些驱动拒绝兼 - 容但仍然有效(比如 Derby) + 允许 JDBC 支持自动生成主键,需要驱动兼容。如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。 true | false @@ -237,10 +192,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, autoMappingBehavior - 指定 MyBatis 如何自动映射列到字段/ - 属性。PARTIAL 只会自动映射简单, - 没有嵌套的结果。FULL 会自动映射任 - 意复杂的结果(嵌套的或其他情况) 。 + 指定 MyBatis 是否以及如何自动映射指定的列到字段或属性。NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。FULL 会自动映射任意复杂的结果集(包括嵌套和其他情况)。 NONE, PARTIAL, FULL @@ -254,10 +206,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, defaultExecutorType - 配置默认的执行器。SIMPLE 执行器没 - 有什么特别之处。REUSE 执行器重用 - 预处理语句。BATCH 执行器重用语句 - 和批量更新 + 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements);BATCH 执行器将重用语句并执行批量更新。 SIMPLE @@ -273,9 +222,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, defaultStatementTimeout - 设置超时时间, - 它决定驱动等待一个数 - 据库响应的时间。 + 设置超时时间,它决定驱动等待数据库响应的秒数。 Any positive integer @@ -289,8 +236,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, safeRowBoundsEnabled - Allows using RowBounds on nested - statements. + 允许在嵌套语句中使用行分界(RowBounds)。 true | false @@ -304,10 +250,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, mapUnderscoreToCamelCase - Enables automatic mapping from - classic database column names - A_COLUMN to camel case classic Java - property names aColumn. + 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 true | false @@ -321,9 +264,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, localCacheScope - MyBatis uses local cache to prevent circular references and speed up repeated nested queries. - By default (SESSION) all queries executed during a session are cached. If localCacheScope=STATEMENT local session will be used just for - statement execution, no data will be shared between two different calls to the same SqlSession. + MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 SESSION | STATEMENT @@ -337,8 +278,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, jdbcTypeForNull - Specifies the JDBC type for null values when no specific JDBC type was provided for the parameter. - Some drivers require specifying the column JDBC type but others work with generic values like NULL, VARCHAR or OTHER. + 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER @@ -352,7 +292,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, lazyLoadTriggerMethods - Specifies which Object's methods trigger a lazy load + 指定哪个对象的方法触发一次延迟加载。 A method name list separated by commas @@ -366,7 +306,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, defaultScriptingLanguage - Specifies the language used by default for dynamic SQL generation. + 指定动态 SQL 生成的默认语言。 A type alias or fully qualified class name. @@ -379,7 +319,8 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, callSettersOnNulls 当结果集中含有Null值时是否执行映射对象的setter或者Map对象的put方法。此设置对于原始类型如int,boolean等无效。 + + 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意原始类型(int、boolean等)是不能设置成 null 的。 true | false @@ -393,7 +334,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, logPrefix - Specifies the prefix string that MyBatis will add to the logger names. + 指定 MyBatis 增加到日志名称的前缀。 Any String @@ -407,7 +348,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, logImpl - Specifies which logging implementation MyBatis should use. If this setting is not present logging implementation will be autodiscovered. + 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING @@ -421,7 +362,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, proxyFactory - Specifies the proxy tool that MyBatis will use for creating lazy loading capable objects. + 为 Mybatis 用来创建具有延迟加载能力的对象设置代理工具。 CGLIB | JAVASSIST @@ -433,7 +374,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment,

    - 一个设置信息元素的示例,完全的配置如下所示: + 一个配置完整的 settings 元素的示例如下:

    @@ -453,12 +394,7 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment,
    -

    - 类型别名是为 Java 类型命名一个短的名字。 -它只和 XML 配置有关, -只用来减少类完全 -限定名的多余部分。例如: -

    +

    类型别名是为 Java 类型命名的一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。例如:

    @@ -468,41 +404,20 @@ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, ]]> -

    -使用这个配置, -“Blog”可以任意用来替代“domain.blog. Blog”所使用的地方。 -

    -

    You can also specify a package where MyBatis will search for - beans. For example: +

    使用这个配置,“Blog”可以用在任何使用“domain.blog.Blog”的地方。

    +

    也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

    ]]> -

    - Each bean found in - domain.blog - , if no annotation is found, - will be registered as an alias - using - uncapitalized non-qualified class name of the bean. Thas is - domain.blog.Author - will be registered as - Author - . If the - @Alias - annotation is found its value will - be used as an alias. See the example below: -

    +

    每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。比如 domain.blog.Author 的别名为 Author;若有注解,则别名为其注解值。看下面的例子:

    -

    -对于普通的 Java 类型,有许多内建的类型别名。它们都是大小写不敏感的,由于重载 -的名字,要注意原生类型的特殊处理。 -

    +

    已经为普通的 Java 类型内建了许多相应的类型别名。它们都是大小写不敏感的,需要注意的是由于重载原始类型的名称所做的特殊处理。

    @@ -735,13 +650,7 @@ public class Author {
    -

    -无论是 MyBatis 在预处理语句中设置一个参数, -还是从结果集中取出一个值时, -类型处 -理器被用来将获取的值以合适的方式转换成 Java 类型。下面这个表格描述了默认的类型处 -理器。 -

    +

    无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。

    @@ -749,7 +658,7 @@ public class Author { 类型处理器 @@ -1008,20 +916,12 @@ public class Author { Enumeration Type
    - Java 类型 + Java 类型 JDBC 类型 @@ -996,8 +905,7 @@ public class Author { Enumeration Type - VARCHAR-任何兼容的字符串类型, - 作为代码存储(而不是索引) + VARCHAR-任何兼容的字符串类型,作为代码存储(而不是索引)
    - Any compatible NUMERIC or DOUBLE, as the position is stored - (not the code itself). + 任何兼容的 NUMERICDOUBLE 类型,作为位置存储(而不是代码本身)。
    -

    - 你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 - //TODO translation needed - - To do so, implement the interface org.apache.ibatis.type.TypeHandler - or extend the convenience class org.apache.ibatis.type.BaseTypeHandler - and optionally map it to a JDBC type. 例如: -

    +

    你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口,或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler,然后可以将它映射到一个 JDBC 类型。比如:

    { @@ -1054,56 +954,33 @@ public class ExampleTypeHandler extends BaseTypeHandler { ]]> -

    - 使用这样的类型处理器将会覆盖已经存在的处理 Java 的 String 类型属性和 VARCHAR -参数及结果的类型处理器。 -要注意 MyBatis 不会审视数据库元信息来决定使用哪种类型, -所 -以你必须在参数和结果映射中指定那是 VARCHAR 类型的字段,来绑定到正确的类型处理 -器上。这是因为 MyBatis 直到语句被执行都不知道数据类型的这个现实导致的。 -

    -

    - // TODO translation needed - MyBatis will know the the Java type that you want to handle with - this TypeHandler by introspecting its generic type, but - you can override this behavior by two means: -

    +

    使用这个的类型处理器将会覆盖已经存在的处理 Java 的 String 类型属性和 VARCHAR 参数及结果的类型处理器。要注意 MyBatis 不会窥探数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明那是 VARCHAR 类型的字段,以使其能够绑定到正确的类型处理器上。这基于一个事实:MyBatis 直到语句被执行才会去关心数据类型。

    +

    MyBatis 会通过窥探属性的原始类型(generic type)来推断由类型处理器处理的 Java 类型,不过这种行为可以通过两种方法改变:

      -
    • Adding a javaType attribute to the typeHandler element (for example: javaType="String") -
    • -
    • Adding a @MappedTypes annotation to your TypeHandler class specifying - the list of java types to associate it with. This annotation will be ignored if - the javaType attribute as also been specified. +
    • 在类型处理器的配置元素(typeHandler element)上增加一个 javaType 属性(比如:javaType="String"); +
    • +
    • 在类型处理器的类上(TypeHandler class)增加一个 @MappedTypes 注解来指定与其关联的 Java 类型列表。 如果在 javaType 属性中也同时指定,则注解方式将被忽略。
    -

    Associated JDBC type can be specified by two means:

    +

    可以通过两种方式来指定被关联的 JDBC 类型:

    • - Adding a jdbcType attribute to the typeHandler element (for example: jdbcType="VARCHAR"). + 在类型处理器的配置元素上增加一个 javaType 属性(比如:javaType="VARCHAR");
    • -
    • Adding a @MappedJdbcTypes annotation to your TypeHandler class specifying - the list of JDBC types to associate it with. This annotation will be ignored if - the jdbcType attribute as also been specified. +
    • 在类型处理器的类上(TypeHandler class)增加一个 @MappedJdbcTypes 注解来指定与其关联的 JDBC 类型列表。 如果在 javaType 属性中也同时指定,则注解方式将被忽略。
    -

    And finally you can let MyBatis search for your TypeHandlers:

    +

    最后,可以让 MyBatis 为你查找类型处理器:

    ]]> -

    - Note that when using the autodiscovery feature JDBC types can only be - specified with annotations. -

    -

    - You can create a generic TypeHandler that is able to handle more than one class. For that purpose - add a constructor that receives the class as a parameter and MyBatis will pass the actual class when - constructing the TypeHandler. -

    +

    注意在使用自动检索(autodiscovery)功能的时候,只能通过注解方式来指定 JDBC 的类型。

    +

    你能创建一个原始的类型处理器,它可以处理多于一个类。为达到此目的,需要增加一个接收该类作为参数的构造器,这样在构造一个类型处理器的时候 MyBatis 就会传入一个具体的类。

    extends BaseTypeHandler { @@ -1117,52 +994,26 @@ public class GenericTypeHandler extends BaseTypeHandler { ... ]]> -

    EnumTypeHandler and EnumOrdinalTypeHandler are generic TypeHandlers. We will learn - about them in the following section. -

    +

    EnumTypeHandlerEnumOrdinalTypeHandler 都是原始的类型处理器(generic TypeHandlers),我们将会在接下来的部分详细探讨。

    - - -

    - If you want to map an Enum, you'll need to use either - EnumTypeHandler or EnumOrdinalTypeHandler. -

    - -

    For example, let's say that we need to store the rounding mode that - should be used with some number if it needs to be rounded. By default, MyBatis - uses EnumTypeHandler to convert the Enum - values to their names. -

    - - Note EnumTypeHandler is special in the sense that unlike other handlers, - it does not handle just one specific class, but any class that extends Enum - -

    However, we may not want to store names. Our DBA may insist on an - integer code instead. That's just as easy: add EnumOrdinalTypeHandler - to the typeHandlers in your config file, and now each - RoundingMode will be mapped to an integer using its ordinal value. -

    + + +

    若想映射枚举类型 Enum,则需要从 EnumTypeHandler 或者 EnumOrdinalTypeHandler 中选一个来使用。

    + +

    比如说我们想存储用来四舍五入一些数字时用到的舍入模式。默认情况下,MyBatis 会利用 EnumTypeHandler 来把 Enum 值转换成对应的名字。

    + + 注意 EnumTypeHandler 在某种意义上来说是比较特别的,其他的处理器只针对某个特定的类,而它不同,它会处理任意继承了 Enum 的类。 + +

    不过,我们可能不会去存储名字,相反我们的 DBA 会坚持使用整形值代码。那也一样轻而易举:在配置文件中把 EnumOrdinalTypeHandler 加到 typeHandlers 中即可,这样每个 RoundingMode 将通过他们的序数值来映射成对应的整形。

    ]]> -

    - But what if you want to map the same Enum to a - string in one place and to integer in another? -

    -

    - The auto-mapper will automatically use EnumOrdinalTypeHandler, - so if we want to go back to using plain old ordinary - EnumTypeHandler, we have to tell it, by explicitly setting - the type handler to use for those SQL statements. -

    -

    - (Mapper files aren't covered until the next section, so if this is your first - time reading through the documentation, you may want to skip this for now - and come back to it later.) -

    +

    但是怎样能将同样的 Enum 既映射成字符串又映射成整形呢?

    +

    自动映射器(auto-mapper)会自动地选用 EnumOrdinalTypeHandler 来处理,所以如果我们想用回旧的序数型的 EnumTypeHandler,就非要为那些 SQL 语句显式地设置要用到的类型处理器不可。

    +

    (下一节才开始讲映射器文件,所以如果是首次阅读该文档,你可能需要先越过这一步,过会再来看。)

    @@ -1205,20 +1056,11 @@ public class GenericTypeHandler extends BaseTypeHandler { ]]> -

    - Note that this forces us to use a resultMap - instead of a resultType in our select statements. -

    +

    注意,这里的 select 语句强制使用 resultMap 来代替 resultType

    - -

    - MyBatis 每次创建结果对象新的实例时, -它使用一个 ObjectFactory 实例来完成。 -如果参 -数映射存在,默认的 ObjectFactory 不比使用默认构造方法或带参数的构造方法实例化目标 -类做的工作多。如果你想重写默认的 ObjectFactory,你可以创建你自己的。比如: -

    + +

    MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现。比如:

    ]]> -

    - ObjectFactory 接口很简单。它包含两个创建用的方法,一个是处理默认构造方法的,另 -外一个是处理带参数构造方法的。最终,setProperties 方法可以被用来配置 ObjectFactory。 -在 初 始化 你 的 ObjectFactory 实例 后 , objectFactory 元素 体 中定 义的 属 性会 被传 递 给 -setProperties 方法。 -

    +

    ObjectFactory 接口很简单,它包含两个创建用的方法,一个是处理默认构造方法的,另外一个是处理带参数的构造方法的。最后,setProperties 方法可以被用来配置 ObjectFactory,在初始化你的 ObjectFactory 实例后,objectFactory 元素体中定义的属性会被传递给 setProperties 方法。

    - +

    - MyBatis 允许你在某一点拦截已映射语句执行的调用。默认情况下,MyBatis 允许使用 -插件来拦截方法调用: + MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

    • @@ -1272,20 +1108,8 @@ setProperties 方法。 (prepare, parameterize, batch, update, query)
    -

    -这些类中方法的详情可以通过查看每个方法的签名来发现 ,而且它们的源代码在 -MyBatis 的发行包中有。你应该理解你覆盖方法的行为,假设你所做的要比监视调用要多。 -如果你尝试修改或覆盖一个给定的方法, -你可能会打破 MyBatis 的核心。 -这是低层次的类和 -方法,要谨慎使用插件。 -

    -

    -使用插件是它们提供的非常简单的力量。 -简单实现拦截器接口, -要确定你想拦截的指定 -签名。 -

    +

    这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 的发行包中的源代码。假设你想做的不仅仅是监控方法的调用,那么你应该很好的了解正在重写的方法的行为。因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。这些都是更低层的类和方法,所以使用插件的时候要特别当心。

    +

    通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定了想要拦截的方法签名即可。

    ]]> -

    -上面的插件将会拦截在 Executor 实例中所有的“update”方法调用,它也是负责低层次 -映射语句执行的内部对象。 -

    +

    上面的插件将会拦截在 Executor 实例中所有的“update”方法调用,这里的 Executor 是负责低层映射语句执行的内部对象。

    NOTE - 覆盖配置类 - -

    -

    -除了用插件来修改 MyBatis 核心行为之外, -你也可以完全覆盖配置类。 -简单扩展它, -然后覆盖其中的任意方法,之后传递它到 sqlSessionFactoryBuilder.build(myConfig)方法 -的调用。这可能会严重影响 MyBatis 的行为,所以要小心。 + 覆盖配置类

    +

    除了用插件来修改 MyBatis 核心行为之外,还可以通过完全覆盖配置类来达到目的。只需继承后覆盖其中的每个方法,再把它传递到 sqlSessionFactoryBuilder.build(myConfig) 方法即可。再次重申,这可能会严重影响 MyBatis 的行为,务请慎之又慎。

    - + +

    MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者共享相同 Schema 的多个生产数据库,想使用相同的 SQL 映射。许多类似的用例。

    -MyBatis 可以配置多种环境。这会帮助你将 SQL 映射应用于多种数据库之中。例如, -你也许为开发要设置不同的配置, -测试和生产环境。 -或者你可能有多种生产级数据库却共享 -相同的模式,所以你会想对不同数据库使用相同的 SQL 映射。这种用例是很多的。 + 不过要记住:尽管可以配置多个环境,每个 SqlSessionFactory 实例只能选择其一。

    - 一个很重要的问题要记得:你可以配置多种环境,但你只能为每个 SqlSessionFactory -实例选择一个。 -

    -

    - 所以,如果你想连接两个数据库,你需要创建两个 SqlSessionFactory 实例,每个数据库 -对应一个。而如果是三个数据库,你就需要三个实例,以此类推。记忆起来很简单: + 所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单:

    • - 每个数据库对应一个 SqlSessionFactory + 每个数据库对应一个 SqlSessionFactory 实例
    -

    -为了明确创建哪种环境,你可以将它作为可选的参数传递给 SqlSessionFactoryBuilder。 -可以接受环境配置的两个方法签名是: +

    为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名是:

    -

    如果环境被忽略,那么默认环境将会被加载,如下进行: +

    如果忽略了环境参数,那么默认环境将会被加载,如下所示:

    ]]>

    - 注意这里的键: + 注意这里的关键点:

    • - 默认的环境 ID(比如:default=”development”)。 + 默认的环境 ID(比如:default=”development”)。
    • - 每个 environment 元素定义的环境 ID(比如:id=”development”)。 + 每个 environment 元素定义的环境 ID(比如:id=”development”)。
    • - 事务管理器的配置(比如:type=”JDBC”)。 + 事务管理器的配置(比如:type=”JDBC”)。
    • - 数据源的配置(比如:type=”POOLED”)。 + 数据源的配置(比如:type=”POOLED”)。
    -

    - 默认的环境和环境 ID 是自我解释的。你可以使用你喜欢的名称来命名,只要确定默认 -的要匹配其中之一。 -

    -

    - transactionManager +

    默认的环境和环境 ID 是自我解释的。你可以使用你喜欢的名称来命名,只要确定默认的要匹配其中之一。

    -在 MyBatis 中有两种事务管理器类型(也就是 type=”[JDBC|MANAGED]”): + 事务管理器(transactionManager)

    +

    在 MyBatis 中有两种类型的事务管理器(也就是 type=”[JDBC|MANAGED]”):

      -
    • -JDBC – 这个配置直接简单使用了 JDBC 的提交和回滚设置。 -它依赖于从数据源得 -到的连接来管理事务范围。 +
    • JDBC – 这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务范围。
    • -
    • -MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接。而它会让 -容器来管理事务的整个生命周期(比如 Spring 或 JEE 应用服务器的上下文) -默认 -情况下它会关闭连接。 -然而一些容器并不希望这样, -因此如果你需要从连接中停止 -它,将 closeConnection 属性设置为 false。例如: +
    • MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。例如: ]]>

    - NOTE - If you are planning to use MyBatis with Spring there is no need to configure - any TransactionManager because the Spring module will set its own one - overriding any previously set configuration. -

    -

    -这两种事务管理器都不需要任何属性。然而它们都是类型别名,要替换使用它们,你需 -要放置将你自己的类的完全限定名或类型别名,它们引用了你对 TransactionFactory 接口的实现 -类。 + NOTE如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

    +

    这两种事务管理器类型都不需要任何属性。不过它们都是类型别名,话剧话说,你需要使用自己的完全限定名或类型别名而不是使用他们,它们引用了你对 TransactionFactory 接口的实现类。

    -

    -任何在 XML 中配置的属性在实例化之后将会被传递给 setProperties()方法。 -你的实现类 -需要创建一个事务接口的实现,这个接口也很简单: -

    +

    任何在 XML 中配置的属性在实例化之后将会被传递给 setProperties() 方法。你的实现类也需要创建一个事务接口的实现,这个接口也很简单:

    +

    使用这两个接口,你可以完全自定义 MyBatis 对事务的处理。

    -使用这两个接口,你可以完全自定义 MyBatis 对事务的处理。 -

    -

    - dataSource -

    -

    -dataSource 元素使用基本的 JDBC 数据源接口来配置 JDBC 连接对象的资源。 + 数据源(dataSource)

    +

    dataSource 元素使用基本的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

      -
    • 许多 MyBatis 的应用程序将会按示例中的例子来配置数据源。 -然而它并不是必须的。 -要知道为了方便使用延迟加载,数据源才是必须的。 +
    • 许多 MyBatis 的应用程序将会按示例中的例子来配置数据源。然而它并不是必须的。要知道为了方便使用延迟加载,数据源才是必须的。
    -

    有三种内建的数据源类型(也就是 type=”???”): -

    +

    有三种内建的数据源类型(也就是 type=”???”):

    - UNPOOLED -– 这个数据源的实现是每次被请求时简单打开和关闭连接。它有一点慢, -这是对简单应用程序的一个很好的选择, -因为它不需要及时的可用连接。 -不同的数据库对这 -个的表现也是不一样的, -所以对某些数据库来说配置数据源并不重要, -这个配置也是闲置的。 -UNPOOLED 类型的数据源仅仅用来配置以下 5 种属性: -

    + 无连接池(UNPOOLED)– 这个数据源的实现是每次被请求时简单打开和关闭连接。它有一点慢,这是对简单应用程序的一个很好的选择,因为它不需要及时的可用连接。不同的数据库对这个的表现也是不一样的,所以对某些数据库来说配置数据源并不重要,这个配置也是闲置的。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:

      -
    • driver – 这是 JDBC 驱动的 Java 类的完全限定名(如果你的驱动包含,它也不是 - 数据源类)。 +
    • driver – 这是 JDBC 驱动的 Java 类的完全限定名(如果你的驱动包含,它也不是数据源类)。
    • url – 这是数据库的 JDBC URL 地址。
    • @@ -1491,90 +1253,53 @@ UNPOOLED 类型的数据源仅仅用来配置以下 5 种属性:
    • defaultTransactionIsolationLevel – 默认的连接事务隔离级别。
    -

    -作为可选项,你可以传递数据库驱动的属性。要这样做,属性的前缀是以“driver.”开 -头的,例如: +

    作为可选项,你也可以传递 properties 给数据库驱动。要这样做,属性的前缀以“driver.”开头,例如:

    • driver.encoding=UTF8
    -

    -这 样 就 会 传 递 以 值 “ UTF8 ” 来 传 递 属 性 “ encoding ”, 它 是 通 过 -DriverManager.getConnection(url,driverProperties)方法传递给数据库驱动。 -

    -

    - POOLED - – 这是 JDBC 连接对象的数据源连接池的实现,用来避免创建新的连接实例 -时必要的初始连接和认证时间。这是一种当前 Web 应用程序用来快速响应请求很流行的方 -法。 +

    这样就会传递以值 “UTF8” 来传递属性“encoding”,它是通过DriverManager.getConnection(url,driverProperties)方法传递给数据库驱动的。

    -除了上述(UNPOOLED)的属性之外,还有很多属性可以用来配置 POOLED 数据源: + 有连接池(POOLED)– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。这是一种使得并发 Web 应用快速响应请求的流行处理方式。

    +

    除了上述无连接池(UNPOOLED)的属性外,会有更多属性用来配置有连接池(POOLED)的数据源:

      -
    • poolMaximumActiveConnections – 在任意时间存在的活动(也就是正在使用)连 -接的数量。默认值:10 +
    • poolMaximumActiveConnections – 在任意时间可以存在的活动(也就是正在使用)连接数量,默认值:10
    • -
    • poolMaximumIdleConnections – 任意时间存在的空闲连接数。 +
    • poolMaximumIdleConnections – 任意时间可能存在的空闲连接数。
    • -
    • poolMaximumCheckoutTime – 在被强制返回之前,池中连接被检查的时间。默认 -值:20000 毫秒(也就是 20 秒) +
    • poolMaximumCheckoutTime – 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)
    • -
    • poolTimeToWait – 这是给连接池一个打印日志状态机会的低层次设置,还有重新 -尝试获得连接, -这些情况下往往需要很长时间 -为了避免连接池没有配置时静默失 -败)。默认值:20000 毫秒(也就是 20 秒) +
    • poolTimeToWait – 这是一个低层设置,如果获取连接花费的相当长的时间,它会给连接池打印日志并重新尝试获取一个连接的机会(避免在误配置的情况下一直安静的失败),默认值:20000 毫秒(即 20 秒)。
    • -
    • poolPingQuery – 发送到数据的侦测查询,用来验证连接是否正常工作,并且准备 -接受请求。默认是“NO PING QUERY SET” -,这会引起许多数据库驱动连接由一 -个错误信息而导致失败。 +
    • poolPingQuery – 发送到数据的侦测查询,用来检验连接是否处在正常工作秩序中并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动失败时带有一个恰当的错误消息。
    • -
    • poolPingEnabled – 这是开启或禁用侦测查询。如果开启,你必须用一个合法的 -SQL 语句(最好是很快速的)设置 poolPingQuery 属性。默认值:false。 +
    • poolPingEnabled – 是否启用侦测查询。若开启,也必须使用一个可执行的 SQL 语句设置 poolPingQuery 属性(最好是一个非常快的 SQL),默认值:false。
    • -
    • poolPingConnectionsNotUsedFor – 这是用来配置 poolPingQuery 多次时间被用一次。 -这可以被设置匹配标准的数据库连接超时时间, -来避免不必要的侦测。 -默认值: -0(也就是所有连接每一时刻都被侦测-但仅仅当 poolPingEnabled 为 true 时适用)。 +
    • poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的使用频度。这可以被设置成匹配标准的数据库连接超时时间,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。

    - JNDI - – 这个数据源的实现是为了使用如 Spring 或应用服务器这类的容器, -容器可以集 -中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。这个数据源配置只需要两个属 -性: + JNDI– 这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。这种数据源配置只需要两个属性:

      -
    • initial_context – 这 个 属 性 用 来 从 初 始 上 下 文 中 寻 找 环 境 ( 也 就 是 -initialContext.lookup(initial——context) 。这是个可选属性,如果被忽略,那么 -data_source 属性将会直接以 initialContext 为背景再次寻找。 +
    • initial_context – 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么 data_source 属性将会直接从 InitialContext 中寻找。
    • -
    • data_source – 这是引用数据源实例位置的上下文的路径。它会以由 initial_context -查询返回的环境为背景来查找,如果 initial_context 没有返回结果时,直接以初始 -上下文为环境来查找。 +
    • data_source – 这是引用数据源实例位置的上下文的路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。
    -

    和其他数据源配置相似, -它也可以通过名为 -“env.” -的前缀直接向初始上下文发送属性。 -比如: +

    和其他数据源配置类似,可以通过名为“env.”的前缀直接向初始上下文发送属性(send properties)。比如:

    • env.encoding=UTF8
    -

    -在初始化之后,这就会以值“UTF8”向初始上下文的构造方法传递名为“encoding” -的属性。 +

    这就会在初始化时以值“UTF8”向初始上下文的构造方法传递名为“encoding”的属性。

    - +

    - You can plug any 3rd party DataSource by implementing the interface org.apache.ibatis.datasource.DataSourceFactory: + 也可使用任何第三方数据源,需要实现这个接口 org.apache.ibatis.datasource.DataSourceFactory

    - org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory can be used as super class - class to build new datasource adapters. For example this is the code needed to plug C3P0: + org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory 可被用作父类来构建新的数据源适配器,比如下面这段插入 C3P0 所必需的代码:

    -

    To set it up, add a property for each setter method you want MyBatis to call. - Below depicts a sample configuration which connects to a PostgreSQL database:

    +

    为了令其工作,可在每个需要 MyBatis 调用的 setter 方法中增加一个属性。下面是一个可以连接至 PostgreSQL 数据库的例子:

    @@ -1607,46 +1330,26 @@ public class C3P0DataSourceFactory extends UnpooledDataSourceFactory { ]]> - +
    - + -

    - MyBatis is able to execute different statements depending on your database vendor. - The multi-db vendor support is based on the mapped statements databaseId attribute. - MyBatis will load all statements with no databaseId attribute - or with a databaseId that matches the current one. If case the same statement - if found with and without the databaseId the latter will be discarded. - To enable the multi vendor support add a databaseIdProvider - to mybatis-config.xml file as follows: -

    - +

    MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性的。MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。为支持多厂商特性只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可:

    + ]]> -

    - The DB_VENDOR implementation databaseIdProvider sets as a databaseId the String returned by - DatabaseMetaData#getDatabaseProductName(). - As usually that string is too long and also, different versions of the same product return different values, - so you may want to translate it to a shorter one by adding properties like follows: -

    - +

    这里的 DB_VENDOR 会通过 DatabaseMetaData#getDatabaseProductName() 返回的字符串进行设置。由于通常情况下这个字符串都非常长而且相同产品的不同版本会返回不同的值,所以最好通过设置属性别名来使其变短,如下:

    + ]]> -

    - When properties are provided, the DB_VENDOR databaseIdProvider will search the property value corresponding to the - first key found in the returned database product name or "null" if there is not a matching property. - In this case, if getDatabaseProductName() returns "Oracle (DataDirect)" the databaseId will be set to "oracle". -

    - -

    - You can build your own database provider by implementing the interface org.apache.ibatis.mapping.DatabaseIdProvider - and registerin it in mybatis-config.xml: -

    +

    在有 properties 时,DB_VENDOR databaseIdProvider 元素会搜索所返回数据库产品名称的第一个对应键值,如果没有匹配的将会返回 null。在这个例子中,如果 getDatabaseProductName() 返回“Oracle (DataDirect)”,databaseId 将被设置为“oracle”。

    + +

    你可以通过实现接口 org.apache.ibatis.mapping.DatabaseIdProvider 并在 mybatis-config.xml 中注册来构建自己的 database provider:

    - - -

    -既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要定义 SQL 映射语句了。 -但是, -首先我们需要告诉 MyBatis 到哪里去找到这些语句。 -Java 在这方面没有提供一个很好 -的方法, -所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。 -你可以使用相对于类路径的 -资源引用,或者字符表示,或 url 引用的完全限定名(包括 file:///URLs) -。例如: -

    + + +

    既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要定义 SQL 映射语句了。但是首先我们需要告诉 MyBatis 到哪里去找到这些语句。Java 在自动查找这方面没有提供一个很好的方法,所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 的 URL),或类名和包名等。例如:

    @@ -1693,10 +1386,7 @@ Java 在这方面没有提供一个很好 ]]> -

    -这些语句简单告诉了 MyBatis 去哪里找映射文件。其余的细节就是在每个 SQL 映射文 -件中了,下面的部分我们来讨论 SQL 映射文件。 -

    +

    这些配置会告诉了 MyBatis 去哪里找映射文件,剩下的细节就应该是每个 SQL 映射文件了,也就是接下来我们要讨论的。

    diff --git a/src/site/zh/xdoc/dynamic-sql.xml b/src/site/zh/xdoc/dynamic-sql.xml index 305fda252..bfd3b4c2f 100644 --- a/src/site/zh/xdoc/dynamic-sql.xml +++ b/src/site/zh/xdoc/dynamic-sql.xml @@ -18,59 +18,39 @@ + xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd"> MyBatis 3 | 动态 SQL Clinton Begin Nan Lei + Dongxu Wang -
    -

    -MyBatis 的一个强大的特性之一通常是它的动态 SQL 能力。 -如果你有使用 JDBC 或其他 -相似框架的经验,你就明白条件地串联 SQL 字符串在一起是多么的痛苦,确保不能忘了空 -格或在列表的最后省略逗号。动态 SQL 可以彻底处理这种痛苦。 -

    -

    -通常使用动态 SQL 不可能是独立的一部分,MyBatis 当然使用一种强大的动态 SQL 语 -言来改进这种情形,这种语言可以被用在任意映射的 SQL 语句中。 -

    -

    -动态 SQL 元素和使用 JSTL 或其他相似的基于 XML 的文本处理器相似。在 MyBatis 之 -前的版本中,有很多的元素需要来了解。MyBatis 3 大大提升了它们,现在用不到原先一半 -的元素就能工作了。MyBatis 采用功能强大的基于 OGNL 的表达式来消除其他元素。 -

    -
      -
    • if
    • -
    • choose (when, otherwise)
    • -
    • trim (where, set)
    • -
    • foreach
    • -
    - -

    -在动态 SQL 中所做的最通用的事情是包含部分 where 字句的条件。比如: -

    - +

    MyBatis 的强大特性之一便是它的动态 SQL 能力。如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 字符串有多么痛苦。拼接的时候要确保不能忘了必要的空格,还要注意省掉列名列表最后的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。

    +

    通常使用动态 SQL 不可能是独立的一部分,MyBatis 当然使用一种强大的动态 SQL 语言来改进这种情形,这种语言可以被用在任意映射的 SQL 语句中。

    +

    动态 SQL 元素和使用 JSTL 或其他相似的基于 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多的元素需要来了解。MyBatis 3 大大提升了它们,现在用不到原先一半的元素就能工作了。MyBatis 采用功能强大的基于 OGNL 的表达式来消除其他元素。

    +
      +
    • if
    • +
    • choose (when, otherwise)
    • +
    • trim (where, set)
    • +
    • foreach
    • +
    + +

    动态 SQL 通常要做的事情是有条件地包含 where 子句的一部分。比如:

    + SELECT * FROM BLOG WHERE state = ‘ACTIVE’ AND title like #{title} -]]> -

    -这条语句会提供一个可选的文本查找功能。如果你没有传递 title,那么所有激活的博客 -都会被返回。但是如果你传递了 title,那么就会查找相近的 title(对于敏锐的检索,这中情 -况下你的参数值需要包含任意的遮掩或通配符)的博客。 -

    -

    -假若我们想可选地搜索 title 和 author 呢?首先,要改变语句的名称让它有意义。然后 -简单加入另外的一个条件。 -

    - ]]> +

    这条语句提供了一个可选的文本查找类型的功能。如果没有传入“title”,那么所有处于“ACTIVE”状态的BLOG都会返回;反之若传入了“title”,那么就会把相近“title”的BLOG返回(就这个例子而言,细心的读者会发现其中的参数值是可以包含一些掩码或通配符的)。

    +

    如果想可选地通过“title”和“author”两个条件搜索该怎么办呢?首先,改变语句的名称让它更具实际意义;然后只要加入另一个条件即可。

    + SELECT * FROM BLOG WHERE state = ‘ACTIVE’ @@ -80,20 +60,11 @@ MyBatis 的一个强大的特性之一通常是它的动态 SQL 能力。 AND author_name like #{author.name} ]]> -
    - -

    -有时我们不想应用所有的条件, -相反我们想选择很多情况下的一种。 Java 中的 switch -和 -语句相似,MyBatis 提供 choose 元素。 -

    -

    -我们使用上面的示例,但是现在我们来搜索当 title 提供时仅有 title 条件,当 author 提 -供时仅有 author 条件。如果二者都没提供,只返回 featured blogs(也许是由管理员策略地选 -择的结果列表,而不是返回大量没有意义的随机博客结果列表)。 -

    - + +

    有些时候,我们不想用到所有的条件语句,而只想从中择其一二。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

    +

    还是上面的例子,但是这次变为提供了“title”就按“title”查找,提供了“author”就按“author”查找,若两者都没有提供,就返回所有符合条件的BLOG(实际情况可能是由管理员策略地选出BLOG列表,而不是返回大量无意义的随机结果)。

    + SELECT * FROM BLOG WHERE state = ‘ACTIVE’ @@ -108,13 +79,10 @@ MyBatis 的一个强大的特性之一通常是它的动态 SQL 能力。 ]]> -
    - -

    -前面的例子已经方便地处理了一个臭名昭著的动态 SQL 问题。要考虑我们回到“if”示 -例后会发生什么,但是这次我们将“ACTIVE = 1”也设置成动态的条件。 -

    - + +

    前面几个例子已经合宜地解决了一个臭名昭著的动态 SQL 问题。现在考虑回到“if”示例,这次我们将“ACTIVE = 1”也设置成动态的条件,看看会发生什么。

    + SELECT * FROM BLOG WHERE @@ -128,27 +96,17 @@ MyBatis 的一个强大的特性之一通常是它的动态 SQL 能力。 AND author_name like #{author.name} ]]> -

    -如果这些条件都没有匹配上将会发生什么?这条 SQL 结束时就会成这样: -

    - 如果这些条件没有一个能匹配上将会怎样?最终这条 SQL 会变成这样:

    + -

    -这会导致查询失败。如果仅仅第二个条件匹配是什么样的?这条 SQL 结束时就会是这 -样: -

    - 这会导致查询失败。如果仅仅第二个条件匹配又会怎样?这条 SQL 最终会是这样: +

    + -

    -这个查询也会失败。这个问题不能简单的用条件来解决,如果你从来没有这样写过,那 -么你以后也不会这样来写。 -

    -

    -MyBatis 有一个简单的处理,这在 90%的情况下都会有用。而在不能使用的地方,你可 -以自定义处理方式。加上一个简单的改变,所有事情都会顺利进行: -

    - 这个查询也会失败。这个问题不能简单的用条件句式来解决,如果你也曾经被迫这样写过,那么你很可能从此以后都不想再这样去写。

    +

    MyBatis 有一个简单的处理,这在90%的情况下都会有用。而在不能使用的地方,你可以自定义处理方式来令其正常工作。一处简单的修改就能得到想要的效果:

    + SELECT * FROM BLOG @@ -162,30 +120,15 @@ MyBatis 有一个简单的处理,这在 90%的情况下都会有用。而在不 AND author_name like #{author.name} -]]> -

    -where 元素知道如果由被包含的标记返回任意内容,就仅仅插入“WHERE” -。而且,如 -果以“AND”或“OR”开头的内容,那么就会跳过 WHERE 不插入。 -

    -

    -如果 where 元素没有做出你想要的,你可以使用 trim 元素来自定义。比如,和 where -元素相等的 trim 元素是: -

    - +]]> +

    where 元素知道只有在一个以上的if条件有值的情况下才去插入“WHERE”子句。而且,若最后的内容是“AND”或“OR”开头的,where 元素也知道如何将他们去除。

    +

    如果 where 元素没有按正常套路出牌,我们还是可以通过自定义 trim 元素来定制我们想要的功能。比如,和 where 元素等价的自定义 trim 元素为:

    + ... -]]> -

    -prefixOverrides 属性采用管道文本分隔符来覆盖, -这里的空白也是重要的。 -它的结果就是移除 -在 prefixOverrides 属性中指定的内容,插入在 with 属性中的内容。 -

    -

    -和动态更新语句相似的解决方案是 set。set 元素可以被用于动态包含更新的列,而不包 -含不需更新的。比如: -

    - +]]> +

    prefixOverrides 属性会忽略通过管道分隔的文本序列(注意此例中的空格也是必要的)。它带来的结果就是所有在 prefixOverrides 属性中指定的内容将被移除,并且插入 prefix 属性中指定的内容。

    +

    类似的用于动态更新语句的解决方案叫做 set。set 元素可以被用于动态包含需要更新的列,而舍去其他的。比如:

    + update Author username=#{username}, @@ -195,27 +138,16 @@ prefixOverrides 属性采用管道文本分隔符来覆盖, where id=#{id} ]]> -

    -这里,set 元素会动态前置 SET 关键字,而且也会消除任意无关的逗号,那也许在应用 -条件之后来跟踪定义的值。 -

    -

    -如果你对和这相等的 trim 元素好奇,它看起来就是这样的: -

    - +

    这里,set 元素会动态前置 SET 关键字,同时也会消除无关的逗号,因为用了条件语句之后很可能就会在生成的赋值语句的后面留下这些逗号。

    +

    若你对等价的自定义 trim 元素的样子感兴趣,那这就应该是它的真面目:

    + ... ]]> -

    -注意这种情况下我们覆盖一个后缀,而同时也附加前缀。 -

    -
    - -

    -另外一个动态 SQL 通用的必要操作是迭代一个集合, -通常是构建在 IN 条件中的。 -比如: -

    - +

    注意这里我们忽略的是后缀中的值,而又一次附加了前缀中的值。

    +
    + +

    动态 SQL 的另外一个常用的必要操作是需要对一个集合进行遍历,通常是在构建 IN 条件语句的时候。比如:

    + SELECT * FROM POST P WHERE ID in @@ -224,33 +156,22 @@ prefixOverrides 属性采用管道文本分隔符来覆盖, #{item} ]]> -

    -foreach 元素是非常强大的,它允许你指定一个集合,声明集合项和索引变量,它们可 -以用在元素体内。它也允许你指定开放和关闭的字符串,在迭代之间放置分隔符。这个元素 -是很智能的,它不会偶然地附加多余的分隔符。 -

    -

    - 注意 你可以传递一个 List 实例或者数组作为参数对象传给 MyBatis。当你这么做的时 -候,MyBatis 会自动将它包装在一个 Map 中,用名称在作为键。List 实例将会以“list” -作为键,而数组实例将会以“array”作为键。 -

    -

    -这个部分是对关于 XML 配置文件和 XML 映射文件的而讨论的。下一部分将详细讨论 -Java API,所以你可以得到你已经创建的最有效的映射。 -

    -
    - -

    The bind element lets you create a variable out of an OGNL expression and bind it to the context. For example:

    - foreach 元素是非常强大的,它允许你指定一个集合,声明可以用在元素体内的集合项和索引变量。它也允许你指定开闭匹配的字符串以及在迭代之间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。

    +

    注意 你可以将一个 List 实例或者数组作为参数对象传给 MyBatis,当你这么做的时候,MyBatis 会自动将它包装在一个 Map 中并以名称为键。List 实例将会以“list”作为键,而数组实例的键将是“array”。

    +

    到此我们已经完成了涉及 XML 配置文件和 XML 映射文件的讨论。下一部分将详细探讨 Java API,这样才能从已创建的映射中获取最大利益。

    +
    + +

    bind 元素可以从 OGNL 表达式中创建一个变量并将其绑定到上下文。比如:

    + SELECT * FROM BLOG WHERE title LIKE #{pattern} ]]> -
    - -

    If a databaseIdProvider was configured a "_databaseId" variable is available for dynamic code, so you can build different statements depending on database vendor. Have a look at the following example:

    - +
    + +

    一个配置了“_databaseId”变量的 databaseIdProvider 对于动态代码来说是可用的,这样就可以根据不同的数据库厂商构建特定的语句。比如下面的例子:

    + select seq_users.nextval from dual @@ -262,39 +183,39 @@ Java API,所以你可以得到你已经创建的最有效的映射。 insert into users values (#{id}, #{name}) ]]> -
    - -

    Starting from version 3.2 MyBatis supports pluggable scripting languages, - so you can plug a language driver and use that language to write your dynamic - SQL queries.

    -

    There are two built-in languages:

    -
      -
    • xml
    • -
    • raw
    • -
    -

    The xml language is the default one. It is able to execute all the dynamic tags we saw in the previous sections.

    -

    The raw language is in fact the absence of language. When using this setting MyBatis just performs the - parameter substitution and passes the statement to the database driver. As you may guess, the raw language - is faster than the xml language. -

    -

    You can specify the language you want to use in an statement adding the lang attribute as follows: -

    - +
    + +

    MyBatis 从 3.2 开始支持可插拔的脚本语言,因此你可以在插入一种语言的驱动(language driver)之后来写基于这种语言的动态 SQL 查询。

    +

    可以通过实现下面接口的方式来插入一种语言:

    + parameterType); + SqlSource createSqlSource(Configuration configuration, String script, Class parameterType); +}]]> +

    一旦有了自定义的语言驱动,你就可以在 mybatis-config.xml 文件中将它设置位默认语言:

    + + + + + + +]]> +

    除了设置默认语言,你也可以针对特殊的语句指定特定语言,这可以通过如下的 lang 属性来完成: +

    + SELECT * FROM BLOG ]]> -

    Or, in the case you are using mappers, using the @Lang annotation:

    - 或者在你正在使用的映射中加上注解 @Lang 来完成:

    + selectBlog(); }]]> -

    You can also implement your own language driver by implementing the following interface:

    - parameterType); - SqlSource createSqlSource(Configuration configuration, String script, Class parameterType); -}]]> -
    -
    + +

    注意 可以将 Apache Velocity 作为动态语言来使用,更多细节请参考 MyBatis-Velocity 项目。

    + +

    你前面看到的所有 xml 标签都是默认 MyBatis 语言提供的,它是由别名为 xml 语言驱动器 org.apache.ibatis.scripting.xmltags.XmlLanguageDriver 驱动的。

    +
    + diff --git a/src/site/zh/xdoc/getting-started.xml b/src/site/zh/xdoc/getting-started.xml index 8e997469a..086635836 100644 --- a/src/site/zh/xdoc/getting-started.xml +++ b/src/site/zh/xdoc/getting-started.xml @@ -24,22 +24,15 @@ MyBatis 3 | 入门 Clinton Begin Nan Lei + Dongxu Wang
    - -

    - To use the MyBatis you just need to include the - - mybatis-x.x.x.jar - - file in the classpath. -

    -

    - If you are using Maven just add the following dependency to your pom.xml: -

    + +

    想要使用 MyBatis 只需将 mybatis-x.x.x.jar 文件置于 classpath 中。

    +

    如果使用 Maven 构建项目,则需将下面的 dependency 置于 pom.xml 中:

    org.mybatis @@ -49,27 +42,13 @@
    -

    - 每 一 个 MyBatis 的 应 用 程 序 都 以 一 个 SqlSessionFactory 对 象 的 实 例 为 核 心 。 -SqlSessionFactory 对 象 的 实 例 可 以 通 过 SqlSessionFactoryBuilder 对 象 来 获 得 。 -SqlSessionFactoryBuilder 对象可以从 XML 配置文件,或从 Configuration 类的习惯准备的实 -例中构建 SqlSessionFactory 对象。 -

    -

    -从 XML 文件中构建 SqlSessionFactory 的实例非常简单。这里建议你使用类路径下的资 -源文件来配置,但是你可以使用任意的 Reader 实例,这个实例包括由文字形式的文件路径 -或 URL 形式的文件路径 file://来创建。MyBatis 包含了一些工具类,称作为资源,这些工具 -类包含一些方法,这些方法使得从类路径或其他位置加载资源文件更加简单。 -

    +

    每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。SqlSessionFactoryBuilder 可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。

    +

    从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。但是也可以使用任意的 InputStream 实例,包括字符串形式或 URL 形式的文件路径来配置。MyBatis 包含一个叫 Resources 的工具类,它包含一些静态方法,可使从 classpath 或其他位置加载资源文件更容易。

    -

    -XML 配置文件包含对 MyBatis 系统的核心设置,包含获取数据库连接实例的数据源和 -决定事务范围和控制的事务管理器。关于 XML 配置文件的详细内容可以在文档后面找到, -这里给出一个简单的示例: -

    +

    XML 配置文件(configuration XML)中包含了对 MyBatis 系统的核心设置,包含获取数据库连接实例的数据源(DataSource)和决定事务范围和控制方式的事务管理器(TransactionManager)。XML 配置文件的详细内容后面再探讨,这里先给出一个简单的示例:

    ]]> -

    -当然, XML 配置文件中还有很多可以配置的, -在 -上面的示例指出的则是最关键的部分。 -要注意 XML 头部的声明,需要用来验证 XML 文档正确性。environment 元素体中包含对事 -务管理和连接池的环境配置。 -mappers 元素是包含所有 mapper 映射器) -( -的列表, -这些 mapper -的 XML 文件包含 SQL 代码和映射定义信息。 -

    +

    当然,XML 配置文件中还有很多可以配置的,上面的示例指出的则是最关键的部分。要注意 XML 头部的声明,需要用来验证 XML 文档正确性。environment 元素体中包含了事务管理和连接池的环境配置。mappers 元素是包含一组 mapper 映射器(这些 mapper的 XML 文件包含了 SQL 代码和映射定义信息)。

    -

    - 如果你喜欢从 Java 程序而不是 XML 文件中直接创建配置实例, -或创建你自己的配置构 -建器,MyBatis 也提供完整的配置类,提供所有从 XML 文件中加载配置信息的选项。 - -

    +

    如果你更愿意直接从 Java 程序而不是 XML 文件中创建配置,或创建你自己的配置构建器,MyBatis 也提供了完整的配置类,具有所有和 XML 文件相同功能的配置项。

    -

    -注意这种情况下配置是添加映射类。映射类是 Java 类,这些类包含 SQL 映射语句的注 -解从而避免了 XML 文件的依赖, -XML 映射仍然在大多数高级映射 -(比如: -嵌套 Join 映射) -时需要。出于这样的原因,如果存在 XML 配置文件的话,MyBatis 将会自动查找和加载一 -个对等的 XML 文件(这种情况下,基于类路径下的 BlogMapper.class 类的类名,那么 -BlogMapper.xml 将会被加载)。后面我们会了解更多。 -

    +

    注意该例中配置添加了一个映射类(mapper class)。映射类是 Java 类,它们包含 SQL 映射语句的注解从而避免了 XML 文件的依赖,不过,由于 Java 注解的一些限制加之某些 MyBatis 映射的复杂性,XML 映射对于大多数高级映射(比如:嵌套 Join 映射)来说仍然是必须的。鉴于此,如果存在一个对等的 XML 配置文件的话,MyBatis 会自动查找并加载它(这种情况下, BlogMapper.xml 将会基于类路径和 BlogMapper.class 的类名被加载进来)。具体细节稍后继续。

    -

    -现在,我们已经知道如何获取 SqlSessionFactory 对象了,基于同样的启示,我们就可以 -获得 SqlSession 的实例了。 -SqlSession 对象完全包含以数据库为背景的所有执行 SQL 操作的 -方法。你可以用 SqlSession 实例来直接执行已映射的 SQL 语句。例如: -

    +

    既然有了 SqlSessionFactory ,顾名思义,我们就可以从中获得 SqlSession 的实例了。SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。例如:

    -

    -这种方法起到的作用, -和我们使用之前的 MyBatis 版本是相似的, -现在有一种更简洁的 -方法。使用合理描述参数和 SQL 语句返回值的接口(比如 BlogMapper.class) -,这样现在就 -可以至此那个更简单,更安全的代码,没有容易发生的字符串文字和转换的错误。 -

    -

    -例如: -

    +

    诚然这种方式能够正常工作,并且对于之前版本 MyBatis 的用户来说也比较熟悉,不过现在有了一种更直白的方式。通过使用对于给定语句能够合理描述参数和返回值的接口(比如说BlogMapper.class),你现在不但可以执行更清晰和类型安全的代码,而且还不用担心易错的字符串字面值以及强制类型转换。

    +

    例如:

    -

    -现在我们来探究一下这里到底执行了什么。 -

    +

    现在我们来探究一下这里到底是怎么执行的。

    -

    -这里你也许想知道通过 SqlSession 和 Mapper 对象到底执行了什么操作。已映射的 SQL -语句是一个很大的主题, -而且这个主题会贯穿本文档的大部分内容。 -为了给出一个宏观的概 -念,这里有一些示例。 -上面提到的任何一个示例,语句是通过 XML 或注解定义的。我们先来看看 XML。使 -用基于 XML 的映射语言,在过去的几年中使得 MyBatis 非常流行,他为 MyBatis 提供所有 -的特性设置。如果你以前用过 MyBatis,这个概念应该很熟悉了,但是 XML 映射文件也有 -很多的改进,后面我们会详细来说。这里给出一个基于 XML 映射语句的示例,这些语句应 -该可以满足上述示例中 SqlSession 对象的调用。 -

    +

    到这里你或许很想知道 SqlSession 和 Mapper 到底执行了什么操作,而 SQL 映射是个相当大的话题,可能会占去文档的大部分篇幅。不过为了让你能够了解个大概,这里会给出几个如何运行的例子。

    +

    在上面提到的两个例子中,语句应该是一个通过 XML 定义,一个通过注解定义。先看 XML 定义这个,事实上 MyBatis 提供的全部特性可以利用基于 XML 的映射语言来实现,这使得 MyBatis 在过去的数年间得以流行。如果你以前用过 MyBatis,这个概念应该会比较熟悉。不过 XML 映射文件已经有了很多的改进,随着文档的进行会愈发清晰。这里给出一个基于 XML 映射语句的示例,它应该可以满足上述示例中 SqlSession 的调用。

    ]]> -

    -这个简单的例子中看起来有很多额外的东西, -但是也相当简洁了。 -你可以在一个单独的 -XML 映射文件中定义很多的映射语句,除 XML 头部和文档类型声明之外,你可以得到很 -多 方 便 之 处 。 在 文 件 的 剩 余 部 分 是 很 好 的 自 我 解 释 。 在 命 名 空 间 -“com.mybatis.example.BlogMapper”中,它定义了一个名为“selectBlog”的映射语句,这 -样它允许你使用完全限定名 -“org.mybatis.example.BlogMapper.selectBlog” -来调用映射语句, -我们下面示例中所有的写法也是这样的。 -

    +

    对于这个简单的例子来说似乎有点小题大做了,但实际上它是非常轻量的。在一个 XML 映射文件中,你想定义多少个映射语句都是可以的,这样下来,XML 头部和文件类型声明占去的部分就显得微不足道了。文件的剩余部分具有很好的自解释性。在命名空间“com.mybatis.example.BlogMapper”中定义了一个名为“selectBlog”的映射语句,这样它就允许你使用指定的完全限定名“org.mybatis.example.BlogMapper.selectBlog”来调用映射语句,就像上面的例子中做的那样:

    -

    -要注意这个使用完全限定名调用 Java 对象的方法是相似的,这样做是有原因的。这个 -命名可以直接给相同命名空间下的的映射类, -使用一个名称, -参数和返回值和已映射的查询 -语句都一样的方法即可。 -这就允许你非常容易地调用映射器接口中的方法, -这和你前面看到 -的是一样的,下面这个示例中它又出现了。 -

    +

    你可能注意到这和使用完全限定名调用 Java 对象的方法是相似的,之所以这样做是有原因的。这个命名可以直接映射到在命名空间中同名的 Mapper 类,并在已映射的 select 语句中的名字、参数和返回类型匹配成方法。这样你就可以向上面那样很容易地调用这个对应 Mapper 接口的方法。不过让我们再看一遍下面的例子:

    -

    -第二种方式有很多有点,首先它不是基于文字的,那就更安全了。第二,如果你的 IDE -有代码补全功能,那么你可以利用它来操纵已映射的 SQL 语句。第三,不需要强制类型转 -换,同时 BlogMapper 接口可以保持简洁,返回值类型很安全(参数类型也很安全) -。

    +

    第二种方法有很多优势,首先它不是基于字符串常量的,就会更安全;其次,如果你的 IDE 有代码补全功能,那么你可以在有了已映射的 SQL 语句之后利用这个功能。


    +

    提示命名空间的一点注释

    +

    命名空间(Namespaces)在之前版本的 MyBatis 中是可选的,容易引起混淆因此是没有益处的。现在的命名空间则是必须的,目的是希望能比只是简单的使用更长的完全限定名来区分语句更进一步。

    +

    命名空间使得你所见到的接口绑定成为可能,尽管你觉得这些东西未必用得上,你还是应该遵循这里的规定以防哪天你改变了主义。长远考虑,一旦用上了命名空间,并将它置于合适的 Java 包命名空间之下,你将拥有一份更易维护的代码并提高了 MyBatis 的可用性。

    - 重要 - 命名空间的一点注释 -

    -

    - 命名空间 -在之前版本的 MyBatis 中是可选项,非常混乱也没有帮助。现在,命名空间 -是必须的,而且有一个目的,它使用更长的完全限定名来隔离语句。 -

    -

    -命名空间使得接口绑定成为可能,就像你看到的那样,如果之前不了解,那么现在你 -就会使用它们了,你应该按照下面给出示例的来练习,以免改变自己的想法。使用命名空 -间,并将它放在合适的 Java 包空间之下,将会使你的代码变得简洁,在很长的时间内提高 -MyBatis 的作用。 -

    -

    - 命名解析: 为了减少输入量,MyBatis 对所有的命名配置元素使用如下的命名解析规 -则,包括语句,结果映射,缓存等。 + 命名解析:为了减少输入量,MyBatis 对所有的命名配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则。

      -
    • 直接查找完全限定名(比如“com.mypackage.MyMapper.selectAllThings”,如果 -) 发现就使用。 +
    • 完全限定名(比如“com.mypackage.MyMapper.selectAllThings”)将被直接查找并且找到即用。
    • -
    • 短名称(比如“selectAllThings” -)可以用来引用任意含糊的对象。而如果有两个 -或两个以上的(比如“com.foo.selectAllThings ”和“com.bar.selectAllThings” -), 那么就会得到错误报告,说短名称是含糊的,因此就必须使用完全限定名。 +
    • 短名称(比如“selectAllThings”)如果全局唯一也可以作为一个单独的引用。如果不唯一,有两个或两个以上的相同名称(比如“com.foo.selectAllThings ”和“com.bar.selectAllThings”),那么使用时就会收到错误报告说短名称是不唯一的,这种情况下就必须使用完全限定名。

    -

    -如 BlogMapper 这样的映射器类来说,还有一个妙招。它们中间映射的语句可以不需要 -在 XML 中来写,而可以使用 Java 注解来替换。比如,上面的 XML 示例可以如下来替换: -

    +

    对于像 BlogMapper 这样的映射器类(Mapper class)来说,还有另一招来处理映射。它们的映射的语句可以不需要用 XML 来做,取而代之的可以是 Java 注解。比如,上面的 XML 示例可被替换如下:

    -

    -对于简单语句来说,使用注解代码会更加清晰,然而 Java 注解对于复杂语句来说就会 -混乱, -应该限制使用。 -因此, -如果你不得不做复杂的事情, -那么最好使用 XML 来映射语句。 -

    -

    -当然这也取决于你和你的项目团队的决定, -看哪种更适合你来使用, -还有以长久方式来 -使用映射语句的重要性。也就是说,不要将自己局限在一种方式中。你可以轻松地将注解换 -成 XML 映射语句,反之亦然。 -

    +

    对于简单语句来说,注解使代码显得更加简洁,然而 Java 注解对于稍微复杂的语句就会力不从心并且会显得更加混乱。因此,如果你需要做很复杂的事情,那么最好使用 XML 来映射语句。

    +

    选择何种方式以及映射语句的定义的一致性对你来说有多重要这些完全取决于你和你的团队。换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。

    - -

    -理解我们目前已经讨论过的不同范围和生命周期类是很重要的。 -不正确的使用它们会导 -致严重的并发问题。 -

    + +

    理解我们目前已经讨论过的不同范围和生命周期类是至关重要的,因为错误的使用会导致非常严重的并发问题。


    -

    重要 - Object lifecycle and Dependency Injection Frameworks -

    -

    - Dependency Injection frameworks can create thread safe, transactional SqlSessions and mappers - and inject them directly into your beans so you can just forget about their lifecycle. - You may want to have a look at MyBatis-Spring or - MyBatis-Guice sub-projects to know more about using MyBatis with DI frameworks. +

    提示 + 对象的生命周期和依赖注入框架

    +

    依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器(mapper)并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。如果对如何通过依赖注入框架来使用 MyBatis 感兴趣可以研究一下 MyBatis-Spring 或 MyBatis-Guice 两个子项目。


    SqlSessionFactoryBuilder

    -

    -这个类可以被实例化,使用和丢弃。一旦你创建了 SqlSessionFactory 后,这个类就不需 -要存在了。 -因此 SqlSessionFactoryBuilder 实例的最佳范围是方法范围 -(也就是本地方法变量)。 -你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例, -但是最好的方式是 -不需要保持它一直存在来保证所有 XML 解析资源,因为还有更重要的事情要做。 -

    +

    这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳范围是方法范围(也就是本地方法变量)。你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但是最好还是不要让其一直存在以保证所有的 XML 解析资源开放给更重要的事情。

    SqlSessionFactory

    -

    -一旦被创建,SqlSessionFactory 应该在你的应用执行期间都存在。没有理由来处理或重 -新创建它。 -使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次。 -这样的 -操作将被视为是非常糟糕的。 -因此 SqlSessionFactory 的最佳范围是应用范围。 -有很多方法可 -以做到, -最简单的就是使用单例模式或者静态单例模式。 -

    +

    SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏味道(bad smell)”。因此 SqlSessionFactory 的最佳范围是应用范围。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

    SqlSession

    -

    -每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不能被共享,也是线程 -不安全的。因此最佳的范围是请求或方法范围。绝对不能将 SqlSession 实例的引用放在一个 -类的静态字段甚至是实例字段中。 -也绝不能将 SqlSession 实例的引用放在任何类型的管理范 -围中, -比如 Serlvet 架构中的 HttpSession。 -如果你现在正用任意的 Web 框架, -要考虑 SqlSession -放在一个和 HTTP 请求对象相似的范围内。换句话说,基于收到的 HTTP 请求,你可以打开 -了一个 SqlSession,然后返回响应,就可以关闭它了。关闭 Session 很重要,你应该确保使 -用 finally 块来关闭它。下面的示例就是一个确保 SqlSession 关闭的基本模式: -

    +

    每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的范围是请求或方法范围。绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例域也不行。也绝不能将 SqlSession 实例的引用放在任何类型的管理范围中,比如 Serlvet 架构中的 HttpSession。如果你现在正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的范围中。换句话说,每次收到的 HTTP 请求,就可以打开一个 SqlSession;返回一个响应,就关闭它。这个关闭操作是很重要的,你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。下面的示例就是一个确保 SqlSession 关闭的标准模式:

    -

    -在你的代码中一贯地使用这种模式, -将会保证所有数据库资源都正确地关闭 -(假设你没 -有通过你自己的连接关闭,这会给 MyBatis 造成一种迹象表明你要自己管理连接资源) -。

    -

    Mapper 实例

    -

    -映射器是你创建绑定映射语句的接口。映射器接口的实例可以从 SqlSession 中获得。那 -么从技术上来说,当被请求时,任意映射器实例的最宽范围和 SqlSession 是相同的。然而, -映射器实例的最佳范围是方法范围。也就是说,它们应该在使用它们的方法中被请求,然后 -就抛弃掉。它们不需要明确地关闭,那么在请求对象中保留它们也就不是什么问题了,这和 -SqlSession 相似。你也许会发现,在这个水平上管理太多的资源的话会失控。保持简单,将 -映射器放在方法范围内。下面的示例就展示了这个实践: -

    +

    你应该在你的所有的代码中一致性地使用这种模式来保证所有数据库资源都能被正确地关闭。

    +

    映射器实例(Mapper Instances)

    +

    映射器是创建用来绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的。因此从技术层面讲,映射器实例的最大范围是和 SqlSession 相同的,因为它们都是从 SqlSession 里被请求的。尽管如此,映射器实例的最佳范围是方法范围。也就是说,映射器实例应该在调用它们的方法中被请求,用过之后即可废弃。并不需要显示的关闭映射器实例,尽管在整个请求范围(request scope)保持映射器实例也不会有什么问题,但是很快你会发现,像 SqlSession 一样,在这个范围上管理太多的资源的话会难于控制。所以要保持简单,最好把映射器映射器放在方法范围(method scope)内。下面的示例就展示了这个实践:

    MyBatis 3 | 简介 Clinton Begin Nan Lei + Dongxu Wang
    - -

    - MyBatis 是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除 -了几乎所有的 JDBC 代码和参数的手工设置以及结果集的检索。MyBatis 使用简单的 XML -或注解用于配置和原始映射,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java -对象)映射成数据库中的记录。 -

    + +

    MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手工设置参数以及抽取结果集。MyBatis 使用简单的 XML 或注解来配置和映射基本体,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

    - -

    - 如果你发现了本文档的遗漏之处, -或者丢失 MyBatis 特性的说明时, -那么最好的方法就 -是了解一下这个遗漏之处然后把它记录下来。 -

    -

    - Sources of this manual are available in xdoc format at - project's Git - Fork the repository, update them and send a pull request. -

    -

    -你也是本文档的最佳作者,其他用户也会来阅读它的。 + +

    不管你以何种方式发现了文档的不足,或是某些特性尚且没有文档,则你可以做的最好的事情莫过于去研究它并把文档写出来。

    +

    该文档 xdoc 格式的源码文件可通过项目的 Git 获取。Fork 这个源码库,更新后发送一个 pull request 吧。

    +

    你将成为这个文档的最佳作者,像你一样的用户定会过来查阅的。

    + + +

    其他语言版本(Users can read about MyBatis in following translations):

    + +

    Do you want to read about MyBatis in your own native language? Fill an issue providing patches with your + mother tongue documentation!

    +
    diff --git a/src/site/zh/xdoc/java-api.xml b/src/site/zh/xdoc/java-api.xml index a039d615e..a5b83212d 100644 --- a/src/site/zh/xdoc/java-api.xml +++ b/src/site/zh/xdoc/java-api.xml @@ -121,7 +121,7 @@ Environment ... ]]> -

    如果你调用了 一个使用 environment 参数 的 build 方法, 那么 MyBatis 将会使用 +

    如果你调用了 一个使用 environment 参数 方 式的 build 方法, 那么 MyBatis 将会使用 configuration 对象来配置这个 environment。 当然, 如果你指定了一个不合法的 environment, @@ -258,7 +258,7 @@ Connection 和 autoCommit 两者的方法,因为 MyBatis 会使用当前 connect 置。 MyBatis 为事务隔离级别调用使用一个 Java 枚举包装器, 称为 TransactionIsolationLevel, -否 则 它 们 按 预 期 的 方 式 来 工 作 , 并 有 JDBC 支 持 的 5 级 +否则它们按预期的方式来工作,并有 JDBC 支持的 5 级 ( NONE,READ_UNCOMMITTED,READ_COMMITTED,REPEA TABLE_READ,SERIALIZA BLE) @@ -803,13 +803,13 @@ type,method。type 属性是类的完全限 -

    Mapper Annotation Examples
    -

    This example shows using the @SelectKey annotation to retrieve a value from a sequence before an insert:

    +
    映射申明样例
    +

    这个例子展示了如何使用 @SelectKey 注解来在插入前读取数据库序列的值:

    @Insert("insert into table3 (id, name) values(#{nameId}, #{name})") @SelectKey(statement="call next value for TestSequence", keyProperty="nameId", before=true, resultType=int.class) int insertTable3(Name name); -

    This example shows using the @SelectKey annotation to retrieve an identity value after an insert:

    +

    这个例子展示了如何使用 @SelectKey 注解来在插入后读取数据库识别列的值:

    @Insert("insert into table2 (name) values(#{name})") @SelectKey(statement="call identity()", keyProperty="nameId", before=false, resultType=int.class) int insertTable2(Name name); diff --git a/src/site/zh/xdoc/logging.xml b/src/site/zh/xdoc/logging.xml index 86816a10f..be4c907a3 100644 --- a/src/site/zh/xdoc/logging.xml +++ b/src/site/zh/xdoc/logging.xml @@ -166,7 +166,7 @@ log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n]]> log4j.logger.org.mybatis.example.BlogMapper.selectBlog=TRACE -

    看到了把,两种配置没差别! +

    看到了吧,两种配置没差别!

    配置文件log4j.properties的余下内容是针对日志格式的,这一内容已经超出本 @@ -176,4 +176,4 @@ log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n]]>

    - \ No newline at end of file + diff --git a/src/site/zh/xdoc/sqlmap-xml.xml b/src/site/zh/xdoc/sqlmap-xml.xml index 97828a838..6dc7ddbd9 100644 --- a/src/site/zh/xdoc/sqlmap-xml.xml +++ b/src/site/zh/xdoc/sqlmap-xml.xml @@ -24,43 +24,35 @@ MyBatis 3 | Mapper XML 文件 Clinton Begin Nan Lei + Dongxu Wang
    -

    -MyBatis 真正的力量是在映射语句中。这里是奇迹发生的地方。对于所有的力量,SQL -映射的 XML 文件是相当的简单。当然如果你将它们和对等功能的 JDBC 代码来比较,你会 -发现映射文件节省了大约 95%的代码量。MyBatis 的构建就是聚焦于 SQL 的,使其远离于 -普通的方式。 -

    -

    -SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序): -

    +

    MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。

    +

    SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):

    • cache - – 配置给定命名空间的缓存。 + – 给定命名空间的缓存配置。
    • cache-ref - – 从其他命名空间引用缓存配置。 + – 其他命名空间缓存配置的引用。
    • resultMap - – 最复杂,也是最有力量的元素,用来描述如何从数据库结果集中来加 -载你的对象。 + – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
    • parameterMap - – 已经被废弃了!老式风格的参数映射。内联参数是首选,这个元 -素可能在将来被移除。这里不会记录。 + – 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
    • sql - – 可以重用的 SQL 块,也可以被其他语句引用。 + – 可被其他语句引用的可重用语句块。
    • insert @@ -79,44 +71,24 @@ SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺 – 映射查询语句
    -

    下一部分将从语句本身开始来描述每个元素的细节。 -

    +

    下一部分将从语句本身开始来描述每个元素的细节。

    -

    -查询语句是使用 MyBatis 时最常用的元素之一。 -直到你从数据库取出数据时才会发现将 -数据存在数据库中是多么的有价值, -所以许多应用程序查询要比更改数据多的多。 -对于每次 -插入,更新或删除,那也会有很多的查询。这是 MyBatis 的一个基本原则,也是将重心和努 -力放到查询和结果映射的原因。对简单类别的查询元素是非常简单的。比如: +

    查询语句是 MyBatis 中最常用的元素之一,光能把数据存到数据库中价值并不大,如果还能重新取出来才有用,多数应用也都是查询比修改要频繁。对每个插入、更新或删除操作,通常对应多个查询操作。这是 MyBatis 的基本原则之一,也是将焦点和努力放到查询和结果映射的原因。简单查询的 select 元素是非常简单的。比如:

    SELECT * FROM PERSON WHERE ID = #{id} ]]> -

    -这个语句被称作 selectPerson, -使用一个 int -(或 Integer) -类型的参数, -并返回一个 HashMap -类型的对象,其中的键是列名,值是列对应的值。 +

    这个语句被称作 selectPerson,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值。

    -

    -注意参数注释: -

    +

    注意参数符号:

    -

    -这就告诉 MyBatis 创建一个预处理语句参数。 -使用 JDBC, -这样的一个参数在 SQL 中会 -由一个“?”来标识,并被传递到一个新的预处理语句中,就像这样: +

    这就告诉 MyBatis 创建一个预处理语句参数,通过 JDBC,这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中,就像这样:

    -

    -当然, -这需要很多单独的 JDBC 的代码来提取结果并将它们映射到对象实例中, -这就是 -MyBatis 节省你时间的地方。我们需要深入了解参数和结果映射。那些细节部分我们下面来 -了解。 +

    当然,这需要很多单独的 JDBC 的代码来提取结果并将它们映射到对象实例中,这就是 MyBatis 节省你时间的地方。我们需要深入了解参数和结果映射,细节部分我们下面来了解。

    -

    -select 元素有很多属性允许你配置,来决定每条语句的作用细节。 +

    select 元素有很多属性允许你配置,来决定每条语句的作用细节。

    id - 在命名空间中唯一的标识符,可以被用来引用这条语句。 + 在命名空间中唯一的标识符,可以被用来引用这条语句。 parameterType - 将会传入这条语句的参数类的完全限定名或别名。 + 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。 @@ -175,100 +141,72 @@ select 元素有很多属性允许你配置,来决定每条语句的作用细节 parameterMap - -这是引用外部 parameterMap 的已经被废弃的方法。使用内联参数 -映射和 parameterType 属性。 + 这是引用外部 parameterMap 的已经被废弃的方法。使用内联参数映射和 parameterType 属性。 resultType - -从这条语句中返回的期望类型的类的完全限定名或别名。注意集 -合情形,那应该是集合可以包含的类型,而不能是集合本身。使 -用 resultType 或 resultMap,但不能同时使用。 + 从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。使用 resultType 或 resultMap,但不能同时使用。 resultMap - -命名引用外部的 resultMap。 -返回 map 是 MyBatis 最具力量的特性, -对其有一个很好的理解的话, -许多复杂映射的情形就能被解决了。 -使用 resultMap 或 resultType,但不能同时使用。 + 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,对其有一个很好的理解的话,许多复杂映射的情形都能迎刃而解。使用 resultMap 或 resultType,但不能同时使用。 flushCache - -将其设置为 true,不论语句什么时候被带哦用,都会导致缓存被 -清空。默认值:false。 + 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false。 useCache - -将其设置为 true, -将会导致本条语句的结果被缓存。 -默认值: -true。 + 将其设置为 true,将会导致本条语句的结果被二级缓存,默认值:对 select 元素为 true。 timeout - -这个设置驱动程序等待数据库返回请求结果,并抛出异常时间的 -最大等待值。默认不设置(驱动自行处理) + 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。 fetchSize - -这是暗示驱动程序每次批量返回的结果行数。默认不设置(驱动 -自行处理)。 + 这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为 unset(依赖驱动)。 statementType - -STA -TEMENT,PREPARED 或 CALLABLE 的一种。 -这会让 MyBatis -使用选择使用 Statement,PreparedStatement 或 CallableStatement。 -默认值:PREPARED。 + STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 resultSetType - -FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE -中的一种。默认是不设置(驱动自行处理)。 + FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一个,默认值为 unset (依赖驱动)。 databaseId - In case there is a configured databaseIdProvider, MyBatis will load all statements with no databaseId - attribute or with a databaseId that matches the current one. If case the same statement - if found with and without the databaseId the latter will be discarded. + 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。 resultOrdered - This is only applicable for nested result select statements: If this is true, it - is assumed that nested results are contained or grouped together such that when a - new main result row is returned, no references to a previous result row will occur - anymore. This allows nested results to be filled much more memory friendly. Default: - false. + 这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。 + + + + resultSets + 这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的。
    - +

    - 数据变更语句 insert,update 和 delete 在它们的实现中非常相似: + 数据变更语句 insert,update 和 delete 的实现非常接近:

    ]]> - + @@ -306,12 +244,11 @@ FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE - + - @@ -319,78 +256,49 @@ FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE parameterMap - - - - - - -
    Insert, Update and Delete AttributesInsert, Update 和 Delete 的属性
    属性
    id在命名空间中唯一的标识符,可以被用来引用这条语句。命名空间中的唯一标识符,可被用来代表这条语句。
    parameterType -将会传入这条语句的参数类的完全限定名或别名。 + 将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。
    - -这是引用外部 parameterMap 的已经被废弃的方法。使用内联参数 -映射和 parameterType 属性。 + 这是引用外部 parameterMap 的已经被废弃的方法。使用内联参数映射和 parameterType 属性。
    flushCache -将其设置为 true,不论语句什么时候被带哦用,都会导致缓存被清 -空。默认值:false。 + 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:true(对应插入、更新和删除语句)。
    timeout -这个设置驱动程序等待数据库返回请求结果, -并抛出异常时间的最 -大等待值。默认不设置(驱动自行处理)。 + 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。
    statementType -STA -TEMENT,PREPARED 或 CALLABLE 的一种。这会让 MyBatis -使用选择使用 Statement,PreparedStatement 或 CallableStatement。 -默认值:PREPARED。 + STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
    useGeneratedKeys -( 仅 对 insert, update 有 用 ) 这 会 告 诉 MyBatis 使 用 JDBC 的 -getGeneratedKeys 方法来取出由数据(比如:像 MySQL 和 SQL -Server 这样的数据库管理系统的自动递增字段)内部生成的主键。 -默认值:false。 + (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。
    keyProperty -(仅对 insert, update 有用) -标记一个属性, -MyBatis 会通过 getGeneratedKeys -或者通过 insert 语句的 selectKey 子元素设置它的值。 -默认: -不设置。 Can be a comma separated list of property names if multiple generated columns are expected. + (仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
    keyColumn -(仅对 insert, update 有用) -标记一个属性, -MyBatis 会通过 getGeneratedKeys -或者通过 insert 语句的 selectKey 子元素设置它的值。 -默认: -不设置。 Can be a comma separated list of columns names if multiple generated columns are expected. + (仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
    databaseIdIn case there is a configured databaseIdProvider, MyBatis will load all statements with no databaseId - attribute or with a databaseId that matches the current one. If case the same statement - if found with and without the databaseId the latter will be discarded. + 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。
    -

    下面就是 insert,update 和 delete 语句的示例:

    +

    下面就是 insert,update 和 delete 语句的示例:

    insert into Author (id,username,password,email,bio) @@ -410,16 +318,9 @@ MyBatis 会通过 getGeneratedKeys delete from Author where id = #{id} ]]> -

    -如前所述,插入语句有一点多,它有一些属性和子元素用来处理主键的生成。 -

    +

    如前所述,插入语句的配置规则更加丰富,在插入语句里面有一些额外的属性和子元素用来处理主键的生成,而且有多种生成方式。

    -

    -首先,如果你的数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server) -,那么 -你可以设置 useGeneratedKeys=”true”,而且设置 keyProperty 到你已经做好的目标属性上。 -例如,如果上面的 Author 表已经对 id 使用了自动生成的列类型,那么语句可以修改为: -

    +

    首先,如果你的数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),那么你可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置到目标属性上就OK了。例如,如果上面的 Author 表已经对 id 使用了自动生成的列类型,那么语句可以修改为:

    @@ -427,14 +328,10 @@ MyBatis 会通过 getGeneratedKeys values (#{username},#{password},#{email},#{bio}) ]]> -

    -MyBatis 有另外一种方法来处理数据库不支持自动生成类型,或者可能 JDBC 驱动不支 -持自动生成主键时的主键生成问题。 +

    对于不支持自动生成类型的数据库或可能不支持自动生成主键 JDBC 驱动来说,MyBatis 有另外一种方法来生成主键。

    -

    -这里有一个简单(甚至很傻)的示例,它可以生成一个随机 ID(可能你不会这么做, -但是这展示了 MyBatis 处理问题的灵活性,因为它并不真的关心 ID 的生成): +

    这里有一个简单(甚至很傻)的示例,它可以生成一个随机 ID(你最好不要这么做,但这里展示了 MyBatis 处理问题的灵活性及其所关心的广度):

    @@ -446,15 +343,9 @@ MyBatis 有另外一种方法来处理数据库不支持自动生成类型,或 values (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR}) ]]> -

    -在上面的示例中,selectKey 元素将会首先运行,Author 的 id 会被设置,然后插入语句 -会被调用。 -这给你了一个简单的行为在你的数据库中来处理自动生成的主键, -而不需要使你 -的 Java 代码变得复杂。 +

    在上面的示例中,selectKey 元素将会首先运行,Author 的 id 会被设置,然后插入语句会被调用。这给你了一个和数据库中来处理自动生成的主键类似的行为,避免了使 Java 代码变得复杂。

    -

    -selectKey 元素描述如下: +

    selectKey 元素描述如下:

    ]]> - + @@ -473,42 +364,27 @@ selectKey 元素描述如下: - - - - - @@ -516,14 +392,12 @@ CallableStatement 类型。 -

    -这个元素可以被用来定义可重用的 SQL 代码段,可以包含在其他语句中。比如: +

    这个元素可以被用来定义可重用的 SQL 代码段,可以包含在其他语句中。比如:

    id,username,password ]]> -

    -这个 SQL 片段可以被包含在其他语句中,例如: +

    这个 SQL 片段可以被包含在其他语句中,例如:

    @@ -533,12 +407,8 @@ CallableStatement 类型。 ]]>
    - -

    -在之前的语句中, -你已经看到了一些简单参数的示例。 MyBatis 中参数是非常强大的 -在 -元素。对于简单的做法,大概 90%的情况,是不用太多的,比如: + +

    前面的所有语句中你所见到的都是简单参数的例子,实际上参数是 MyBatis 非常强大的元素,对于简单的做法,大概 90% 的情况参数都很少,比如:

    @@ -547,14 +417,7 @@ CallableStatement 类型。 where id = #{id} ]]> -

    -上面的这个示例说明了一个非常简单的命名参数映射。参数类型被设置为“int” -,因此 -这个参数可以被设置成任何内容。 -原生的类型或简单数据类型, -比如整型和没有相关属性的 -字符串,因此它会完全用参数来替代。然而,如果你传递了一个复杂的对象,那么 MyBatis -的处理方式就会有一点不同。比如: +

    上面的这个示例说明了一个非常简单的命名参数映射。参数类型被设置为 int,这样这个参数就可以被设置成任何内容。原生的类型或简单数据类型(比如整型和字符串)因为没有相关属性,它会完全用参数值来替代。然而,如果传入一个复杂的对象,行为就会有一点不同了。比如:

    @@ -562,70 +425,47 @@ CallableStatement 类型。 values (#{id}, #{username}, #{password}) ]]> -

    -如果 User 类型的参数对象传递到了语句中, username 和 password 属性将会被查找, -id、 -然后它们的值就被传递到预处理语句的参数中。 +

    如果 User 类型的参数对象传递到了语句中,id、username 和 password 属性将会被查找,然后将它们的值传入预处理语句的参数中。

    -

    -这点对于传递参数到语句中非常好。但是对于参数映射也有一些其他的特性。 +

    这点对于向语句中传参是比较好的而且又简单,不过参数映射的功能远不止于此。

    -

    -首先,像 MyBatis 的其他部分,参数可以指定一个确定的数据类型。 +

    首先,像 MyBatis 的其他部分一样,参数也可以指定一个特殊的数据类型。

    -

    -像 MyBatis 的剩余部分,javaType 通常可以从参数对象中来去顶,除非对象是一个 -HashMap。那么 javaType 应该被确定来保证使用正确类型处理器。 +

    像 MyBatis 的剩余部分一样,javaType 通常可以从参数对象中来去确定,前提是只要对象不是一个 HashMap。那么 javaType 应该被确定来保证使用正确类型处理器。

    -

    - 注意 如果 null 被当作值来传递,对于所有可能为空的列,JDBC Type 是需要的。以可 -以自己通过阅读预处理语句的 setNull()方法的 JavaDocs 文档来研究这个。 +

    NOTE 如果 null 被当作值来传递,对于所有可能为空的列,JDBC Type 是需要的。你可以自己通过阅读预处理语句的 setNull() 方法的 JavaDocs 文档来研究这种情况。

    -

    -为了自定义类型处理器,你可以指定一个确定的类型处理器类(或别名), 比如: +

    为了以后定制类型处理方式,你也可以指定一个特殊的类型处理器类(或别名),比如:

    -

    -尽管它看起来繁琐,但是实际上是你很少设置它们其中之一。 +

    尽管看起来配置变得越来越繁琐,但实际上是很少去设置它们。

    -

    -对于数值类型,对于决定有多少数字是相关的,有一个数值范围。 +

    对于数值类型,还有一个小数保留位数的设置,来确定小数点后保留的位数。

    -

    -最后,mode 属性允许你指定 IN,OUT 或 INOUT 参数。如果参数为 OUT 或 INOUT, -参数对象属性的真实值将会被改变,就像你期望你需要你个输出参数。如果 mode 为 OUT -(或 INOUT) -,而且 jdbcType 为 CURSOR(也就是 Oracle 的 REFCURSOR) -,你必须指定 -一个 resultMap 来映射结果集到参数类型。要注意这里的 javaType 属性是可选的,如果左边 -的空白是 jdbcType 的 CURSOR 类型,它会自动地被设置为结果集。 +

    最后,mode 属性允许你指定 IN,OUT 或 INOUT 参数。如果参数为 OUT 或 INOUT,参数对象属性的真实值将会被改变,就像你在获取输出参数时所期望的那样。如果 mode 为 OUT(或 INOUT),而且 jdbcType 为 CURSOR(也就是 Oracle 的 REFCURSOR),你必须指定一个 resultMap 来映射结果集到参数类型。要注意这里的 javaType 属性是可选的,如果左边的空白是 jdbcType 的 CURSOR 类型,它会自动地被设置为结果集。

    -

    -MyBatis 也支持很多高级的数据类型,比如结构体,但是当注册 out 参数时你必须告诉 -语句类型名称。比如(再次提示,在实际中不要像这样换行): +

    MyBatis 也支持很多高级的数据类型,比如结构体,但是当注册 out 参数时你必须告诉它语句类型名称。比如(再次提示,在实际中要像这样不能换行):

    -

    -尽管所有这些强大的选项很多时候你只简单指定属性名,MyBatis 会自己计算剩余的。 -最多的情况是你为 jdbcType 指定可能为空的列名。 +

    尽管所有这些强大的选项很多时候你只简单指定属性名,其他的事情 MyBatis 会自己去推断,最多你需要为可能为空的列名指定 jdbcType

    -

    -默认情况下,使用#{}格式的语法会导致 MyBatis 创建预处理语句属性并以它为背景设 -置安全的值(比如?) -。这样做很安全,很迅速也是首选做法,有时你只是想直接在 SQL 语 -句中插入一个不改变的字符串。比如,像 ORDER BY,你可以这样来使用: +

    默认情况下,使用#{}格式的语法会导致 MyBatis 创建预处理语句属性并安全地设置值(比如?)。这样做更安全,更迅速,通常也是首选做法,不过有时你只是想直接在 SQL 语句中插入一个不改变的字符串。比如,像 ORDER BY,你可以这样来使用:

    -

    -这里 MyBatis 不会修改或转义字符串。 +

    这里 MyBatis 不会修改或转义字符串。

    - 重要 接受从用户输出的内容并提供给语句中不变的字符串,这样做是不安全的。这会 -导致潜在的 SQL 注入攻击,因此你不应该允许用户输入这些字段,或者通常自行转义并检 -查。 + NOTE 以这种方式接受从用户输出的内容并提供给语句中不变的字符串是不安全的,会导致潜在的 SQL 注入攻击,因此要么不允许用户输入这些字段,要么自行转义并检验。

    @@ -1804,30 +1637,30 @@ MyBatis 会从结果集中得到每条记录, - +

    - As you have already seen in the previous sections, in simple cases MyBatis can auto-map the results for you - and in others you will need to build a result map. - But as you will see in this section you can also mix both strategies. - Let's have a deeper look at how auto-mapping works. + 正如你在前面一节看到的,在简单的场景下,MyBatis可以替你自动映射查询结果。 + 如果遇到复杂的场景,你需要构建一个result map。 + 但是在本节你将看到,你也可以混合使用这两种策略。 + 让我们到深一点的层面上看看自动映射是怎样工作的。

    - When auto-mapping results MyBatis will get the column name and look for a property with the same name ignoring case. That means that if - a column named ID and property named id are found, MyBatis will set the id property with the ID column value. + 当自动映射查询结果时,MyBatis会获取sql返回的列名并在java类中查找相同名字的属性(忽略大小写)。 + 这意味着如果Mybatis发现了ID列和id属性,Mybatis会将ID的值赋给id

    - Usually database columns are named using uppercase letters and underscores between words and java properties often follow the camelcase - naming covention. To enable the auto-mapping between them set the setting mapUnderscoreToCamelCase to true. + 通常数据库列使用大写单词命名,单词间用下划线分隔;而java属性一般遵循驼峰命名法。 + 为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase设置为true。

    - Auto-mapping works even when there is an specific result map. When this happens, for each result map, all columns that are present in the - ResultSet that have not a manual mapping will be auto-mapped, then manual mappings will be processed. - In the following sample id and userName columns will be auto-mapped and hashed_password column will be mapped.

    - + 自动映射甚至在特定的result map下也能工作。在这种情况下,对于每一个result map,所有的ResultSet提供的列, + 如果没有被手工映射,则将被自动映射。自动映射处理完毕后手工映射才会被处理。 + 在接下来的例子中, iduserName列将被自动映射, hashed_password 列将根据配置映射。 +

    select user_id as "id", diff --git a/src/site/zh/xdoc/statement-builders.xml b/src/site/zh/xdoc/statement-builders.xml index b82dfc8ed..50b0abb39 100644 --- a/src/site/zh/xdoc/statement-builders.xml +++ b/src/site/zh/xdoc/statement-builders.xml @@ -28,6 +28,11 @@
    + + +

    This section is outdated in the Chinese version. Please refer to the English manual. Any help with the Chinese translation will be really welcome.

    +
    +

    一个 Java 程序员面对的最痛苦的事情之一就是在 Java 代码中嵌入 SQL 语句。 通常这么 diff --git a/src/test/java/org/apache/ibatis/binding/BoundAuthorMapper.java b/src/test/java/org/apache/ibatis/binding/BoundAuthorMapper.java index d6cea9851..8e7ef21af 100644 --- a/src/test/java/org/apache/ibatis/binding/BoundAuthorMapper.java +++ b/src/test/java/org/apache/ibatis/binding/BoundAuthorMapper.java @@ -19,6 +19,10 @@ import domain.blog.Post; import domain.blog.Section; import org.apache.ibatis.annotations.*; +import domain.blog.Author; +import domain.blog.Post; +import domain.blog.Section; +import org.apache.ibatis.executor.BatchResult; import org.apache.ibatis.session.RowBounds; import java.util.List; @@ -88,4 +92,8 @@ List findThreeSpecificPosts(@Param("one") int one, RowBounds rowBounds, @Param("two") int two, int three); + + @Flush + List flush(); + } diff --git a/src/test/java/org/apache/ibatis/binding/FlushTest.java b/src/test/java/org/apache/ibatis/binding/FlushTest.java new file mode 100644 index 000000000..dd4c04db7 --- /dev/null +++ b/src/test/java/org/apache/ibatis/binding/FlushTest.java @@ -0,0 +1,93 @@ +/** + * Copyright 2009-2015 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.binding; + +import org.apache.ibatis.BaseDataTest; +import domain.blog.Author; +import domain.blog.Post; +import domain.blog.Section; +import org.apache.ibatis.executor.BatchResult; +import org.apache.ibatis.mapping.Environment; +import org.apache.ibatis.session.*; +import org.apache.ibatis.transaction.TransactionFactory; +import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; +import org.junit.BeforeClass; +import org.junit.Test; + +import javax.sql.DataSource; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +public class FlushTest { + private static SqlSessionFactory sqlSessionFactory; + + @BeforeClass + public static void setup() throws Exception { + DataSource dataSource = BaseDataTest.createBlogDataSource(); + TransactionFactory transactionFactory = new JdbcTransactionFactory(); + Environment environment = new Environment("Production", transactionFactory, dataSource); + Configuration configuration = new Configuration(environment); + configuration.setDefaultExecutorType(ExecutorType.BATCH); + configuration.getTypeAliasRegistry().registerAlias(Post.class); + configuration.getTypeAliasRegistry().registerAlias(Author.class); + configuration.addMapper(BoundAuthorMapper.class); + sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); + } + + @Test + public void invokeFlushStatementsViaMapper() { + + SqlSession session = sqlSessionFactory.openSession(); + + try { + + BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class); + Author author = new Author(-1, "cbegin", "******", "cbegin@nowhere.com", "N/A", Section.NEWS); + List ids = new ArrayList(); + mapper.insertAuthor(author); + ids.add(author.getId()); + mapper.insertAuthor(author); + ids.add(author.getId()); + mapper.insertAuthor(author); + ids.add(author.getId()); + mapper.insertAuthor(author); + ids.add(author.getId()); + mapper.insertAuthor(author); + ids.add(author.getId()); + + // test + List results = mapper.flush(); + + assertThat(results.size(), is(1)); + assertThat(results.get(0).getUpdateCounts().length, is(ids.size())); + + for (int id : ids) { + Author selectedAuthor = mapper.selectAuthor(id); + assertNotNull(id + " is not found.", selectedAuthor); + } + + session.rollback(); + } finally { + session.close(); + } + + } + +} diff --git a/src/test/java/org/apache/ibatis/builder/CustomizedSettingsMapperConfig.xml b/src/test/java/org/apache/ibatis/builder/CustomizedSettingsMapperConfig.xml new file mode 100644 index 000000000..400c70096 --- /dev/null +++ b/src/test/java/org/apache/ibatis/builder/CustomizedSettingsMapperConfig.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/apache/ibatis/builder/XmlConfigBuilderTest.java b/src/test/java/org/apache/ibatis/builder/XmlConfigBuilderTest.java index f188c1c4c..9e07f9ce8 100644 --- a/src/test/java/org/apache/ibatis/builder/XmlConfigBuilderTest.java +++ b/src/test/java/org/apache/ibatis/builder/XmlConfigBuilderTest.java @@ -15,26 +15,37 @@ */ package org.apache.ibatis.builder; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - import java.io.InputStream; import java.io.StringReader; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import org.apache.ibatis.builder.xml.XMLConfigBuilder; +import org.apache.ibatis.executor.loader.cglib.CglibProxyFactory; +import org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory; import org.apache.ibatis.io.Resources; +import org.apache.ibatis.logging.slf4j.Slf4jImpl; +import org.apache.ibatis.scripting.defaults.RawLanguageDriver; +import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; +import org.apache.ibatis.session.AutoMappingBehavior; import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.LocalCacheScope; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.TypeHandler; import org.apache.ibatis.type.TypeHandlerRegistry; import org.junit.Test; +import static org.hamcrest.core.Is.*; +import static org.hamcrest.core.IsInstanceOf.*; +import static org.junit.Assert.*; + public class XmlConfigBuilderTest { @Test @@ -44,6 +55,28 @@ public void shouldSuccessfullyLoadMinimalXMLConfigFile() throws Exception { XMLConfigBuilder builder = new XMLConfigBuilder(inputStream); Configuration config = builder.parse(); assertNotNull(config); + assertThat(config.getAutoMappingBehavior(), is(AutoMappingBehavior.PARTIAL)); + assertThat(config.isCacheEnabled(), is(true)); + assertThat(config.getProxyFactory(), is(instanceOf(CglibProxyFactory.class))); + assertThat(config.isLazyLoadingEnabled(), is(false)); + assertThat(config.isAggressiveLazyLoading(), is(true)); + assertThat(config.isMultipleResultSetsEnabled(), is(true)); + assertThat(config.isUseColumnLabel(), is(true)); + assertThat(config.isUseGeneratedKeys(), is(false)); + assertThat(config.getDefaultExecutorType(), is(ExecutorType.SIMPLE)); + assertNull(config.getDefaultStatementTimeout()); + assertNull(config.getDefaultFetchSize()); + assertThat(config.isMapUnderscoreToCamelCase(), is(false)); + assertThat(config.isSafeRowBoundsEnabled(), is(false)); + assertThat(config.getLocalCacheScope(), is(LocalCacheScope.SESSION)); + assertThat(config.getJdbcTypeForNull(), is(JdbcType.OTHER)); + assertThat(config.getLazyLoadTriggerMethods(), is((Set) new HashSet(Arrays.asList("equals", "clone", "hashCode", "toString")))); + assertThat(config.isSafeResultHandlerEnabled(), is(true)); + assertThat(config.getDefaultScriptingLanuageInstance(), is(instanceOf(XMLLanguageDriver.class))); + assertThat(config.isCallSettersOnNulls(), is(false)); + assertNull(config.getLogPrefix()); + assertNull(config.getLogImpl()); + assertNull(config.getConfigurationFactory()); } enum MyEnum { @@ -102,4 +135,37 @@ public void registerJavaTypeInitializingTypeHandler() { assertTrue(typeHandler instanceof EnumOrderTypeHandler); assertArrayEquals(MyEnum.values(), ((EnumOrderTypeHandler) typeHandler).constants); } + + @Test + public void shouldSuccessfullyLoadXMLConfigFile() throws Exception { + String resource = "org/apache/ibatis/builder/CustomizedSettingsMapperConfig.xml"; + InputStream inputStream = Resources.getResourceAsStream(resource); + XMLConfigBuilder builder = new XMLConfigBuilder(inputStream); + Configuration config = builder.parse(); + + assertThat(config.getAutoMappingBehavior(), is(AutoMappingBehavior.NONE)); + assertThat(config.isCacheEnabled(), is(false)); + assertThat(config.getProxyFactory(), is(instanceOf(JavassistProxyFactory.class))); + assertThat(config.isLazyLoadingEnabled(), is(true)); + assertThat(config.isAggressiveLazyLoading(), is(false)); + assertThat(config.isMultipleResultSetsEnabled(), is(false)); + assertThat(config.isUseColumnLabel(), is(false)); + assertThat(config.isUseGeneratedKeys(), is(true)); + assertThat(config.getDefaultExecutorType(), is(ExecutorType.BATCH)); + assertThat(config.getDefaultStatementTimeout(), is(10)); + assertThat(config.getDefaultFetchSize(), is(100)); + assertThat(config.isMapUnderscoreToCamelCase(), is(true)); + assertThat(config.isSafeRowBoundsEnabled(), is(true)); + assertThat(config.getLocalCacheScope(), is(LocalCacheScope.STATEMENT)); + assertThat(config.getJdbcTypeForNull(), is(JdbcType.NULL)); + assertThat(config.getLazyLoadTriggerMethods(), is((Set) new HashSet(Arrays.asList("equals", "clone", "hashCode", "toString", "xxx")))); + assertThat(config.isSafeResultHandlerEnabled(), is(false)); + assertThat(config.getDefaultScriptingLanuageInstance(), is(instanceOf(RawLanguageDriver.class))); + assertThat(config.isCallSettersOnNulls(), is(true)); + assertThat(config.getLogPrefix(), is("mybatis_")); + assertThat(config.getLogImpl().getName(), is(Slf4jImpl.class.getName())); + assertThat(config.getConfigurationFactory().getName(), is(String.class.getName())); + + } + } diff --git a/src/test/java/org/apache/ibatis/builder/xml/dynamic/DynamicSqlSourceTest.java b/src/test/java/org/apache/ibatis/builder/xml/dynamic/DynamicSqlSourceTest.java index e675764e5..48456aefb 100644 --- a/src/test/java/org/apache/ibatis/builder/xml/dynamic/DynamicSqlSourceTest.java +++ b/src/test/java/org/apache/ibatis/builder/xml/dynamic/DynamicSqlSourceTest.java @@ -317,6 +317,20 @@ public void shouldIterateOnceForEachItemInCollection() throws Exception { assertEquals("__frch_item_2", boundSql.getParameterMappings().get(2).getProperty()); } + @Test + public void shouldSkipForEachWhenCollectionIsEmpty() throws Exception { + final HashMap parameterObject = new HashMap() {{ + put("array", new Integer[] {}); + }}; + final String expected = "SELECT * FROM BLOG"; + DynamicSqlSource source = createDynamicSqlSource(new TextSqlNode("SELECT * FROM BLOG"), + new ForEachSqlNode(new Configuration(), mixedContents( + new TextSqlNode("#{item}")), "array", null, "item", "WHERE id in (", ")", ",")); + BoundSql boundSql = source.getBoundSql(parameterObject); + assertEquals(expected, boundSql.getSql()); + assertEquals(0, boundSql.getParameterMappings().size()); + } + @Test public void shouldPerformStrictMatchOnForEachVariableSubstitution() throws Exception { final Map param = new HashMap(); diff --git a/src/test/java/org/apache/ibatis/executor/BaseExecutorTest.java b/src/test/java/org/apache/ibatis/executor/BaseExecutorTest.java index 33e82971b..9c491e08f 100644 --- a/src/test/java/org/apache/ibatis/executor/BaseExecutorTest.java +++ b/src/test/java/org/apache/ibatis/executor/BaseExecutorTest.java @@ -58,6 +58,7 @@ public BaseExecutorTest() { config.setMultipleResultSetsEnabled(true); config.setUseColumnLabel(true); config.setDefaultStatementTimeout(5000); + config.setDefaultFetchSize(100); } @Test diff --git a/src/test/java/org/apache/ibatis/executor/ExecutorTestHelper.java b/src/test/java/org/apache/ibatis/executor/ExecutorTestHelper.java index b812fdc7c..0b596cac8 100644 --- a/src/test/java/org/apache/ibatis/executor/ExecutorTestHelper.java +++ b/src/test/java/org/apache/ibatis/executor/ExecutorTestHelper.java @@ -198,7 +198,7 @@ public static MappedStatement prepareSelectAllAuthorsAutoMappedStatement(final C } }).build()); } - }).build(); + }).fetchSize(1000).build(); } public static MappedStatement prepareSelectOneAuthorMappedStatementWithConstructorResults(final Configuration config) { diff --git a/src/test/java/org/apache/ibatis/parsing/GenericTokenParserTest.java b/src/test/java/org/apache/ibatis/parsing/GenericTokenParserTest.java index 9dcf1c397..67b928c5d 100644 --- a/src/test/java/org/apache/ibatis/parsing/GenericTokenParserTest.java +++ b/src/test/java/org/apache/ibatis/parsing/GenericTokenParserTest.java @@ -73,6 +73,8 @@ public void shallNotInterpolateSkippedVaiables() { assertEquals("${skipped} variable", parser.parse("\\${skipped} variable")); assertEquals("This is a ${skipped} variable", parser.parse("This is a \\${skipped} variable")); + assertEquals("null ${skipped} variable", parser.parse("${skipped} \\${skipped} variable")); + assertEquals("The null is ${skipped} variable", parser.parse("The ${skipped} is \\${skipped} variable")); } @Test(timeout = 1000) diff --git a/src/test/java/org/apache/ibatis/submitted/call_setters_on_nulls/CallSettersOnNullsTest.java b/src/test/java/org/apache/ibatis/submitted/call_setters_on_nulls/CallSettersOnNullsTest.java index fd2b87a26..8cf627c48 100644 --- a/src/test/java/org/apache/ibatis/submitted/call_setters_on_nulls/CallSettersOnNullsTest.java +++ b/src/test/java/org/apache/ibatis/submitted/call_setters_on_nulls/CallSettersOnNullsTest.java @@ -107,7 +107,12 @@ public void shouldCallNullOnMapForSingleColumnWithResultMap() { try { Mapper mapper = sqlSession.getMapper(Mapper.class); List> oneColumns = mapper.getNameOnlyMapped(); - Assert.assertNotNull(oneColumns.get(1)); +// Assert.assertNotNull(oneColumns.get(1)); + // TEST changed after fix for #307 + // When callSetterOnNull is true, setters are called with null values + // but if all the values for an object are null + // the object itself should be null (same as default behaviour) + Assert.assertNull(oneColumns.get(1)); } finally { sqlSession.close(); } diff --git a/src/test/java/org/apache/ibatis/submitted/dynsql/DynSqlMapper.java b/src/test/java/org/apache/ibatis/submitted/dynsql/DynSqlMapper.java new file mode 100644 index 000000000..6e1e7f5b4 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/dynsql/DynSqlMapper.java @@ -0,0 +1,23 @@ +/*- + * Copyright 2009-2015 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ibatis.submitted.dynsql; + +import org.apache.ibatis.annotations.Param; + +public interface DynSqlMapper { + String selectDescription(@Param("p") String p); +} diff --git a/src/test/java/org/apache/ibatis/submitted/dynsql/DynSqlMapper.xml b/src/test/java/org/apache/ibatis/submitted/dynsql/DynSqlMapper.xml new file mode 100644 index 000000000..825558aca --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/dynsql/DynSqlMapper.xml @@ -0,0 +1,33 @@ + + + + + + + + \ No newline at end of file diff --git a/src/test/java/org/apache/ibatis/submitted/dynsql/DynSqlTest.java b/src/test/java/org/apache/ibatis/submitted/dynsql/DynSqlTest.java index 05f7a8e89..37d052b37 100644 --- a/src/test/java/org/apache/ibatis/submitted/dynsql/DynSqlTest.java +++ b/src/test/java/org/apache/ibatis/submitted/dynsql/DynSqlTest.java @@ -158,4 +158,16 @@ public void testOgnlStaticMethodCall() { } } + @Test + public void testBindNull() { + SqlSession sqlSession = sqlSessionFactory.openSession(); + try { + DynSqlMapper mapper = sqlSession.getMapper(DynSqlMapper.class); + String description = mapper.selectDescription(null); + assertEquals("Pebbles", description); + } finally { + sqlSession.close(); + } + } + } diff --git a/src/test/java/org/apache/ibatis/submitted/dynsql/MapperConfig.xml b/src/test/java/org/apache/ibatis/submitted/dynsql/MapperConfig.xml index 018e5edfa..ce2fd8330 100644 --- a/src/test/java/org/apache/ibatis/submitted/dynsql/MapperConfig.xml +++ b/src/test/java/org/apache/ibatis/submitted/dynsql/MapperConfig.xml @@ -38,6 +38,7 @@ + diff --git a/src/test/java/org/apache/ibatis/submitted/foreach/ForEachTest.java b/src/test/java/org/apache/ibatis/submitted/foreach/ForEachTest.java index 4cda2bca7..5131c23a9 100644 --- a/src/test/java/org/apache/ibatis/submitted/foreach/ForEachTest.java +++ b/src/test/java/org/apache/ibatis/submitted/foreach/ForEachTest.java @@ -107,4 +107,21 @@ public void shouldHandleMoreComplexNullItem() { } } + @Test + public void nullItemInContext() { + SqlSession sqlSession = sqlSessionFactory.openSession(); + try { + Mapper mapper = sqlSession.getMapper(Mapper.class); + User user1 = new User(); + user1.setId(3); + List users = new ArrayList(); + users.add(user1); + users.add(null); + String name = mapper.selectWithNullItemCheck(users); + Assert.assertEquals("User3", name); + } finally { + sqlSession.close(); + } + } + } diff --git a/src/test/java/org/apache/ibatis/submitted/foreach/Mapper.java b/src/test/java/org/apache/ibatis/submitted/foreach/Mapper.java index ce7105b5a..4d2f789d9 100644 --- a/src/test/java/org/apache/ibatis/submitted/foreach/Mapper.java +++ b/src/test/java/org/apache/ibatis/submitted/foreach/Mapper.java @@ -25,4 +25,6 @@ public interface Mapper { int countByBestFriend(List users); + String selectWithNullItemCheck(List users); + } diff --git a/src/test/java/org/apache/ibatis/submitted/foreach/Mapper.xml b/src/test/java/org/apache/ibatis/submitted/foreach/Mapper.xml index 79eca87b8..0404b85ff 100644 --- a/src/test/java/org/apache/ibatis/submitted/foreach/Mapper.xml +++ b/src/test/java/org/apache/ibatis/submitted/foreach/Mapper.xml @@ -50,4 +50,16 @@ + + diff --git a/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/CreateDB1.sql b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/CreateDB1.sql new file mode 100644 index 000000000..64b23373a --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/CreateDB1.sql @@ -0,0 +1,33 @@ +-- Copyright 2009-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. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + + DROP PROCEDURE GetOrderDetailsAndHeaders IF EXISTS; + DROP TABLE order_detail IF EXISTS; + DROP TABLE order_header IF EXISTS; + + CREATE TABLE order_detail + ( + order_id integer NOT NULL, + line_number integer NOT NULL, + quantity integer NOT NULL, + item_description varchar(50) NOT NULL, + PRIMARY KEY (order_id, line_number) + ); + + CREATE TABLE order_header + ( + order_id integer NOT NULL, + cust_name varchar(50) NOT NULL, + PRIMARY KEY (order_id) + ); diff --git a/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/CreateDB2.sql b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/CreateDB2.sql new file mode 100644 index 000000000..63336300f --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/CreateDB2.sql @@ -0,0 +1,30 @@ +-- Copyright 2009-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. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + + CREATE PROCEDURE GetOrderDetailsAndHeaders() + READS SQL DATA + DYNAMIC RESULT SETS 1 + BEGIN ATOMIC + + DECLARE result1 CURSOR FOR + SELECT * FROM order_detail + FOR READ ONLY ; + + DECLARE result2 CURSOR FOR + SELECT * FROM order_header + FOR READ ONLY ; + + OPEN result1 ; + OPEN result2 ; + END; diff --git a/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/CreateDB3.sql b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/CreateDB3.sql new file mode 100644 index 000000000..c2fc14d92 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/CreateDB3.sql @@ -0,0 +1,33 @@ +-- Copyright 2009-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. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + + INSERT INTO order_header(order_id, cust_name) + VALUES (1, 'Fred'); + INSERT INTO order_header(order_id, cust_name) + VALUES (2, 'Barney'); + INSERT INTO order_header(order_id, cust_name) + VALUES (3, 'Homer'); + + INSERT INTO order_detail(order_id, line_number, quantity, item_description) + VALUES (1, 1, 1, 'Pen'); + INSERT INTO order_detail(order_id, line_number, quantity, item_description) + VALUES (1, 2, 3, 'Pencil'); + INSERT INTO order_detail(order_id, line_number, quantity, item_description) + VALUES (1, 3, 2, 'Notepad'); + INSERT INTO order_detail(order_id, line_number, quantity, item_description) + VALUES (2, 1, 1, 'Compass'); + INSERT INTO order_detail(order_id, line_number, quantity, item_description) + VALUES (2, 2, 1, 'Protractor'); + INSERT INTO order_detail(order_id, line_number, quantity, item_description) + VALUES (2, 3, 2, 'Pencil'); diff --git a/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/Mapper.java b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/Mapper.java new file mode 100644 index 000000000..216657c61 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/Mapper.java @@ -0,0 +1,24 @@ +/* + * Copyright 2009-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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.multipleresultsetswithassociation; + +import java.util.List; + +public interface Mapper { + + List getOrderDetailsWithHeaders(); + +} \ No newline at end of file diff --git a/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/Mapper.xml b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/Mapper.xml new file mode 100644 index 000000000..87b801b0a --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/Mapper.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/MultipleResultSetTest.java b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/MultipleResultSetTest.java new file mode 100644 index 000000000..425364e05 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/MultipleResultSetTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2009-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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.multipleresultsetswithassociation; + +import java.io.IOException; +import java.io.Reader; +import java.sql.Connection; +import java.util.List; + +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.jdbc.ScriptRunner; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +/* + * This class contains tests for multiple result sets with an association mapping. + * This test is based on the org.apache.ibatis.submitted.multiple_resultsets test. + * + */ +public class MultipleResultSetTest { + + private static SqlSessionFactory sqlSessionFactory; + + @BeforeClass + public static void setUp() throws Exception { + Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/multipleresultsetswithassociation/mybatis-config.xml"); + sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); + reader.close(); + + // populate in-memory database + // Could not get the table creation, procedure creation, and data population to work from the same script. + // Once it was in three scripts, all seemed well. + SqlSession session = sqlSessionFactory.openSession(); + Connection conn = session.getConnection(); + reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/multipleresultsetswithassociation/CreateDB1.sql"); + runReaderScript(conn, session, reader); + reader.close(); + reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/multipleresultsetswithassociation/CreateDB2.sql"); + runReaderScript(conn, session, reader); + reader.close(); + reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/multipleresultsetswithassociation/CreateDB3.sql"); + runReaderScript(conn, session, reader); + reader.close(); + session.close(); + } + + private static void runReaderScript(Connection conn, SqlSession session, Reader reader) throws Exception { + ScriptRunner runner = new ScriptRunner(conn); + runner.setLogWriter(null); + runner.setSendFullScript(true); + runner.setAutoCommit(true); + runner.setStopOnError(false); + runner.runScript(reader); + } + + @Test + public void shouldGetOrderDetailsEachHavingAnOrderHeader() throws IOException { + SqlSession sqlSession = sqlSessionFactory.openSession(); + try { + Mapper mapper = sqlSession.getMapper(Mapper.class); + List orderDetails = mapper.getOrderDetailsWithHeaders(); + + // There are six order detail records in the database + // As long as the data does not change this should be successful + Assert.assertEquals(6, orderDetails.size()); + + // Each order detail should have a corresponding OrderHeader + // Only 2 of 6 orderDetails have orderHeaders + for(OrderDetail orderDetail : orderDetails){ + Assert.assertNotNull(orderDetail.getOrderHeader()); + } + + } finally { + sqlSession.close(); + } + } + +} diff --git a/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/OrderDetail.java b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/OrderDetail.java new file mode 100644 index 000000000..2f99e5ad5 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/OrderDetail.java @@ -0,0 +1,66 @@ +/* + * Copyright 2009-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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.multipleresultsetswithassociation; + +public class OrderDetail { + + private int orderId; + private int lineNumber; + private int quantity; + private String itemDescription; + + private OrderHeader orderHeader; + + public int getOrderId() { + return orderId; + } + + public void setOrderId(int orderId) { + this.orderId = orderId; + } + + public int getLineNumber() { + return lineNumber; + } + + public void setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public String getItemDescription() { + return itemDescription; + } + + public void setItemDescription(String itemDescription) { + this.itemDescription = itemDescription; + } + + public OrderHeader getOrderHeader() { + return orderHeader; + } + + public void setOrderHeader(OrderHeader orderHeader) { + this.orderHeader = orderHeader; + } +} diff --git a/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/OrderHeader.java b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/OrderHeader.java new file mode 100644 index 000000000..1726625d2 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/OrderHeader.java @@ -0,0 +1,39 @@ +/* + * Copyright 2009-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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.multipleresultsetswithassociation; + +public class OrderHeader { + + private int orderId; + private String custName; + + public int getOrderId() { + return orderId; + } + + public void setOrderId(int orderId) { + this.orderId = orderId; + } + + public String getCustName() { + return custName; + } + + public void setCustName(String custName) { + this.custName = custName; + } + +} diff --git a/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/mybatis-config.xml b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/mybatis-config.xml new file mode 100644 index 000000000..02edede5b --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/multipleresultsetswithassociation/mybatis-config.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/Account.java b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/Account.java new file mode 100644 index 000000000..356a1bb62 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/Account.java @@ -0,0 +1,61 @@ +/*- + * Copyright 2009-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ibatis.submitted.nestedresulthandler_association; + +import java.util.Date; + +public class Account { + private String accountUuid; + + private String accountName; + + private Date birthDate; + + private AccountAddress address; + + public String getAccountUuid() { + return accountUuid; + } + + public void setAccountUuid(String accountUuid) { + this.accountUuid = accountUuid; + } + + public String getAccountName() { + return accountName; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public Date getBirthDate() { + return birthDate; + } + + public void setBirthDate(Date birthDate) { + this.birthDate = birthDate; + } + + public AccountAddress getAddress() { + return address; + } + + public void setAddress(AccountAddress address) { + this.address = address; + } +} diff --git a/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/AccountAddress.java b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/AccountAddress.java new file mode 100644 index 000000000..2e8b9c8ba --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/AccountAddress.java @@ -0,0 +1,49 @@ +/*- + * Copyright 2009-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ibatis.submitted.nestedresulthandler_association; + +public class AccountAddress { + private String accountUuid; + + private String zipCode; + + private String address; + + public String getAccountUuid() { + return accountUuid; + } + + public void setAccountUuid(String accountUuid) { + this.accountUuid = accountUuid; + } + + public String getZipCode() { + return zipCode; + } + + public void setZipCode(String zipCode) { + this.zipCode = zipCode; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } +} diff --git a/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/AccountRepository.xml b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/AccountRepository.xml new file mode 100644 index 000000000..be854417a --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/AccountRepository.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/CreateDB.sql b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/CreateDB.sql new file mode 100644 index 000000000..394f1f2aa --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/CreateDB.sql @@ -0,0 +1,37 @@ +-- +-- Copyright 2009-2014 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. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +DROP TABLE t_account_address if exists; +DROP TABLE t_account if exists; +CREATE TABLE t_account( + account_uuid CHAR(36) PRIMARY KEY + ,account_name VARCHAR(256) + ,birth_date DATETIME +); +CREATE TABLE t_account_address( + account_uuid CHAR(36) PRIMARY KEY + ,zip_code CHAR(7) + ,address VARCHAR(256) +); + +INSERT INTO t_account (account_uuid,account_name,birth_date) VALUES ('4d3c8bdd-5379-4aeb-bc56-fcb01eb7cc01', 'Bob1', '2014-01-01 00:00:00.000'); +INSERT INTO t_account (account_uuid,account_name,birth_date) VALUES ('4d3c8bdd-5379-4aeb-bc56-fcb01eb7cc02', 'Bob2', '2014-01-02 00:00:00.000'); +INSERT INTO t_account (account_uuid,account_name,birth_date) VALUES ('4d3c8bdd-5379-4aeb-bc56-fcb01eb7cc03', 'Bob3', '2014-01-03 00:00:00.000'); +INSERT INTO t_account (account_uuid,account_name,birth_date) VALUES ('4d3c8bdd-5379-4aeb-bc56-fcb01eb7cc04', 'Bob4', '2014-01-04 00:00:00.000'); +INSERT INTO t_account (account_uuid,account_name,birth_date) VALUES ('4d3c8bdd-5379-4aeb-bc56-fcb01eb7cc05', 'Bob5', '2014-01-05 00:00:00.000'); +INSERT INTO t_account (account_uuid,account_name,birth_date) VALUES ('4d3c8bdd-5379-4aeb-bc56-fcb01eb7cc11', 'Mark1', '2014-02-01 00:00:00.000'); + +INSERT INTO t_account_address (account_uuid,zip_code,address) VALUES ('4d3c8bdd-5379-4aeb-bc56-fcb01eb7cc02', '1710051', 'Tokyo Toshimaku Nagasaki'); diff --git a/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/NestedResultHandlerAssociationTest.java b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/NestedResultHandlerAssociationTest.java new file mode 100644 index 000000000..328011a99 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/NestedResultHandlerAssociationTest.java @@ -0,0 +1,106 @@ +/* + * Copyright 2009-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.nestedresulthandler_association; + +import static org.junit.Assert.*; + +import java.io.Reader; +import java.sql.Connection; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.jdbc.ScriptRunner; +import org.apache.ibatis.session.ResultContext; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.junit.BeforeClass; +import org.junit.Test; + +public class NestedResultHandlerAssociationTest { + + private static SqlSessionFactory sqlSessionFactory; + + @BeforeClass + public static void setUp() throws Exception { + // create an SqlSessionFactory + Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/nestedresulthandler_association/mybatis-config.xml"); + sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); + reader.close(); + + // populate in-memory database + SqlSession session = sqlSessionFactory.openSession(); + Connection conn = session.getConnection(); + reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/nestedresulthandler_association/CreateDB.sql"); + ScriptRunner runner = new ScriptRunner(conn); + runner.setLogWriter(null); + runner.runScript(reader); + reader.close(); + session.close(); + } + + @Test + public void shouldHandleRowBounds() throws Exception { + SqlSession sqlSession = sqlSessionFactory.openSession(); + final SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd"); + Date targetMonth = fmt.parse("2014-01-01"); + final List accounts = new ArrayList(); + try { + sqlSession.select("collectPageByBirthMonth", targetMonth, new RowBounds(1, 2), new ResultHandler() { + @Override + public void handleResult(ResultContext context) { + Account account = (Account) context.getResultObject(); + accounts.add(account); + } + }); + } finally { + sqlSession.close(); + } + assertEquals(2, accounts.size()); + assertEquals("Bob2", accounts.get(0).getAccountName()); + assertEquals("Bob3", accounts.get(1).getAccountName()); + } + + @Test + public void shouldHandleStop() throws Exception { + SqlSession sqlSession = sqlSessionFactory.openSession(); + final SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd"); + final List accounts = new ArrayList(); + try { + Date targetMonth = fmt.parse("2014-01-01"); + sqlSession.select("collectPageByBirthMonth", targetMonth, new ResultHandler() { + @Override + public void handleResult(ResultContext context) { + Account account = (Account) context.getResultObject(); + accounts.add(account); + if (accounts.size() > 1) + context.stop(); + } + }); + } finally { + sqlSession.close(); + } + assertEquals(2, accounts.size()); + assertEquals("Bob1", accounts.get(0).getAccountName()); + assertEquals("Bob2", accounts.get(1).getAccountName()); + } + +} diff --git a/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/mybatis-config.xml b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/mybatis-config.xml new file mode 100644 index 000000000..a2f5a8b78 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_association/mybatis-config.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/Binome.java b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/Binome.java new file mode 100644 index 000000000..9ee6b6102 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/Binome.java @@ -0,0 +1,66 @@ +/* + * Copyright 2009-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.nestedresulthandler_multiple_association; + +public class Binome { + private T one; + private U two; + + public Binome() { + } + + public Binome(final T one, final U two) { + this.one = one; + this.two = two; + } + + public T getOne() { + return one; + } + + public void setOne(T one) { + this.one = one; + } + + public U getTwo() { + return two; + } + + public void setTwo(U two) { + this.two = two; + } + + @Override + public int hashCode() { + return (one != null ? one.hashCode() : 0) + + (two != null ? two.hashCode() : 0); + } + + @Override + public boolean equals(final Object obj) { + if (obj instanceof Binome) { + Binome bin = (Binome) obj; + return one != null && one.equals(bin.getOne()) && two != null + && two.equals(bin.getTwo()); + } + return super.equals(obj); + } + + @Override + public String toString() { + return "Binome [one=" + one + ", two=" + two + "]"; + } +} diff --git a/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/ChildBean.java b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/ChildBean.java new file mode 100644 index 000000000..04d1ded6e --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/ChildBean.java @@ -0,0 +1,42 @@ +/* + * Copyright 2009-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.nestedresulthandler_multiple_association; + +public class ChildBean { + private Integer id; + private String value; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return "ChildBean [id=" + id + ", value=" + value + "]"; + } +} diff --git a/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/CreateDB.sql b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/CreateDB.sql new file mode 100644 index 000000000..029942543 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/CreateDB.sql @@ -0,0 +1,45 @@ +-- +-- Copyright 2009-2014 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. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +DROP TABLE parent if exists; +DROP TABLE child if exists; +create table parent( + id integer, + value varchar(20) +); + +create table child( + id integer, + value varchar(20) +); + +create table parent_child( + idparent integer, + idchild_from integer, + idchild_to integer +); + +insert into parent (id, value) values (1, 'parent1'); +insert into parent (id, value) values (2, 'parent2'); + +insert into child (id, value) values (1, 'child1'); +insert into child (id, value) values (2, 'child2'); +insert into child (id, value) values (3, 'child3'); +insert into child (id, value) values (4, 'child4'); + +insert into parent_child (idparent, idchild_from, idchild_to) values (1, 1, 2); +insert into parent_child (idparent, idchild_from, idchild_to) values (2, 2, 3); +insert into parent_child (idparent, idchild_from, idchild_to) values (2, 1, 2); \ No newline at end of file diff --git a/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/NestedResultHandlerMultipleAssociationTest.java b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/NestedResultHandlerMultipleAssociationTest.java new file mode 100644 index 000000000..830f33dbc --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/NestedResultHandlerMultipleAssociationTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2009-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.nestedresulthandler_multiple_association; + +import java.io.Reader; +import java.sql.Connection; +import java.util.List; + +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.jdbc.ScriptRunner; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class NestedResultHandlerMultipleAssociationTest { + + private static SqlSessionFactory sqlSessionFactory; + + @BeforeClass + public static void setUp() throws Exception { + // create an SqlSessionFactory + Reader reader = Resources + .getResourceAsReader("org/apache/ibatis/submitted/nestedresulthandler_multiple_association/mybatis-config.xml"); + sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); + reader.close(); + + // populate in-memory database + SqlSession session = sqlSessionFactory.openSession(); + Connection conn = session.getConnection(); + reader = Resources + .getResourceAsReader("org/apache/ibatis/submitted/nestedresulthandler_multiple_association/CreateDB.sql"); + ScriptRunner runner = new ScriptRunner(conn); + runner.setLogWriter(null); + runner.runScript(reader); + reader.close(); + session.close(); + } + + @Test + public void failure() throws Exception { + SqlSession sqlSession = sqlSessionFactory.openSession(); + + // Parents have child going from somewhere to somewhere, they are stored in + // a Binome object + // In this test we have 2 parents: + // Parent1 is going from Child1 to Child2 + // Parent2 is going from Child2 to Child3 and from Child1 to Child2 + // You'll see a NULL entry in the list instead of the Binome Child1/Child2 + List list = sqlSession.selectList("selectParentBeans"); + for (ParentBean pb : list) { + for (Binome childs : pb.getChilds()) { + Assert.assertNotNull(childs); + Assert.assertNotNull(childs.getOne()); + Assert.assertNotNull(childs.getTwo()); + } + } + + sqlSession.close(); + } + + @Test + public void success() throws Exception { + SqlSession sqlSession = sqlSessionFactory.openSession(); + + ParentBean parent = sqlSession.selectOne("selectParentBeanById", 2); + + // If you only select the Parent2 it works + for (Binome childs : parent.getChilds()) { + Assert.assertNotNull(childs); + Assert.assertNotNull(childs.getOne()); + Assert.assertNotNull(childs.getTwo()); + } + sqlSession.close(); + } + +} diff --git a/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/ParentBean.java b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/ParentBean.java new file mode 100644 index 000000000..327626faa --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/ParentBean.java @@ -0,0 +1,43 @@ +package org.apache.ibatis.submitted.nestedresulthandler_multiple_association; + +import java.util.List; + +public class ParentBean { + private Integer id; + private String value; + private List> childs; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public List> getChilds() { + return childs; + } + + public void setChilds(List> childs) { + this.childs = childs; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("ParentBean [id=" + id + ", value=" + + value + "]\nChilds:\n"); + for (Binome binome : childs) { + sb.append("\tChild : ").append(binome).append('\n'); + } + return sb.toString(); + } +} diff --git a/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/mapper.xml b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/mapper.xml new file mode 100644 index 000000000..eb7f75775 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/mapper.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/mybatis-config.xml b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/mybatis-config.xml new file mode 100644 index 000000000..6080b46ea --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/nestedresulthandler_multiple_association/mybatis-config.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/apache/ibatis/submitted/primitive_array/CreateDB.sql b/src/test/java/org/apache/ibatis/submitted/primitive_array/CreateDB.sql new file mode 100644 index 000000000..3b9b65e8e --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/primitive_array/CreateDB.sql @@ -0,0 +1,30 @@ +-- +-- Copyright 2009-2014 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. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +drop table users if exists; + +create table users ( + id int, + name varchar(20) +); +create table udata ( + user_id int, + num int +); + +insert into users (id, name) values(1, 'User1'); +insert into udata (user_id, num) values(1, 100); +insert into udata (user_id, num) values(1, 300); diff --git a/src/test/java/org/apache/ibatis/submitted/primitive_array/Mapper.java b/src/test/java/org/apache/ibatis/submitted/primitive_array/Mapper.java new file mode 100644 index 000000000..76b1d419c --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/primitive_array/Mapper.java @@ -0,0 +1,22 @@ +/* + * Copyright 2009-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.primitive_array; + +public interface Mapper { + + User getUser(Integer id); + +} diff --git a/src/test/java/org/apache/ibatis/submitted/primitive_array/Mapper.xml b/src/test/java/org/apache/ibatis/submitted/primitive_array/Mapper.xml new file mode 100644 index 000000000..7195901d0 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/primitive_array/Mapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/apache/ibatis/submitted/primitive_array/PrimitiveArrayTest.java b/src/test/java/org/apache/ibatis/submitted/primitive_array/PrimitiveArrayTest.java new file mode 100644 index 000000000..ebbaabc15 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/primitive_array/PrimitiveArrayTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2009-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.primitive_array; + +import java.io.Reader; +import java.sql.Connection; + +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.jdbc.ScriptRunner; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class PrimitiveArrayTest { + + private static SqlSessionFactory sqlSessionFactory; + + @BeforeClass + public static void setUp() throws Exception { + // create an SqlSessionFactory + Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/primitive_array/mybatis-config.xml"); + sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); + reader.close(); + + // populate in-memory database + SqlSession session = sqlSessionFactory.openSession(); + Connection conn = session.getConnection(); + reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/primitive_array/CreateDB.sql"); + ScriptRunner runner = new ScriptRunner(conn); + runner.setLogWriter(null); + runner.runScript(reader); + reader.close(); + session.close(); + } + + @Test + public void shouldGetAUser() { + SqlSession sqlSession = sqlSessionFactory.openSession(); + try { + Mapper mapper = sqlSession.getMapper(Mapper.class); + User user = mapper.getUser(1); + Assert.assertEquals("User1", user.getName()); + Assert.assertEquals(2, user.getNum().length); + Assert.assertEquals(100, user.getNum()[0]); + } finally { + sqlSession.close(); + } + } + +} diff --git a/src/test/java/org/apache/ibatis/submitted/primitive_array/User.java b/src/test/java/org/apache/ibatis/submitted/primitive_array/User.java new file mode 100644 index 000000000..262b4e72d --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/primitive_array/User.java @@ -0,0 +1,47 @@ +/* + * Copyright 2009-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.primitive_array; + +public class User { + + private Integer id; + private String name; + private int[] num; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int[] getNum() { + return num; + } + + public void setNum(int[] num) { + this.num = num; + } +} diff --git a/src/test/java/org/apache/ibatis/submitted/primitive_array/mybatis-config.xml b/src/test/java/org/apache/ibatis/submitted/primitive_array/mybatis-config.xml new file mode 100644 index 000000000..513d74c7c --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/primitive_array/mybatis-config.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + +

    selectKey AttributesselectKey 的属性
    属性
    keyProperty -selectKey 语句结果应该被设置的目标属性。 Can be a comma separated list of property names if multiple generated columns are expected. + selectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
    keyColumnThe column name(s) in the returned result set that match the properties. - Can be a comma separated list of column names if multiple generated columns - are expected. + 匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
    resultType -结果的类型。MyBatis 通常可以算出来,但是写上也没有问题。 -MyBatis 允许任何简单类型用作主键的类型,包括字符串。 + 结果的类型。MyBatis 通常可以推算出来,但是为了更加确定写上也不会有什么问题。MyBatis 允许任何简单类型用作主键的类型,包括字符串。如果希望作用于多个生成的列,则可以使用一个包含期望属性的 Object 或一个 Map。
    order -这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那 -么它会首先选择主键, -设置 keyProperty 然后执行插入语句。 -如果 -设置为 AFTER,那么先执行插入语句,然后是 selectKey 元素- -这和如 Oracle 数据库相似,可以在插入语句中嵌入序列调用。 + 这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先选择主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 元素 - 这和像 Oracle 的数据库相似,在插入语句内部可能有嵌入索引调用。
    statementType -和前面的相 同,MyBatis 支持 STA -TEMENT ,PREPARED 和 -CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 -CallableStatement 类型。 + 与前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 CallableStatement 类型。