From 4dec29f6a1e4fd0633286b5d60d2df0274a4689b Mon Sep 17 00:00:00 2001 From: abel533 Date: Mon, 3 Dec 2018 10:55:54 +0800 Subject: [PATCH 1/8] Add LanguageDriver to ProviderSqlSource --- .../annotation/MapperAnnotationBuilder.java | 2 +- .../builder/annotation/ProviderSqlSource.java | 15 ++- .../submitted/sqlprovider/BaseMapper.java | 20 ++++ .../submitted/sqlprovider/OurSqlBuilder.java | 108 ++++++++++++++++++ .../sqlprovider/SqlProviderTest.java | 59 ++++++++++ .../ibatis/submitted/sqlprovider/User.java | 3 +- 6 files changed, 201 insertions(+), 6 deletions(-) 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 68d7ce47e5f..c05919282e4 100644 --- a/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java +++ b/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java @@ -484,7 +484,7 @@ private SqlSource getSqlSourceFromAnnotations(Method method, Class parameterT return buildSqlSourceFromStrings(strings, parameterType, languageDriver); } else if (sqlProviderAnnotationType != null) { Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType); - return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method); + return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method, languageDriver); } return null; } catch (Exception e) { diff --git a/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java b/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java index 98079b31e44..7d07876598d 100644 --- a/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java +++ b/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java @@ -21,11 +21,11 @@ import java.util.Map; import org.apache.ibatis.builder.BuilderException; -import org.apache.ibatis.builder.SqlSourceBuilder; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.SqlSource; import org.apache.ibatis.parsing.PropertyParser; import org.apache.ibatis.reflection.ParamNameResolver; +import org.apache.ibatis.scripting.LanguageDriver; import org.apache.ibatis.session.Configuration; /** @@ -35,8 +35,8 @@ public class ProviderSqlSource implements SqlSource { private final Configuration configuration; - private final SqlSourceBuilder sqlSourceParser; private final Class providerType; + private final LanguageDriver languageDriver; private Method providerMethod; private String[] providerMethodArgumentNames; private Class[] providerMethodParameterTypes; @@ -55,10 +55,17 @@ public ProviderSqlSource(Configuration configuration, Object provider) { * @since 3.4.5 */ public ProviderSqlSource(Configuration configuration, Object provider, Class mapperType, Method mapperMethod) { + this(configuration, provider, mapperType, mapperMethod, configuration.getDefaultScriptingLanguageInstance()); + } + + /** + * @since 3.4.6 + */ + public ProviderSqlSource(Configuration configuration, Object provider, Class mapperType, Method mapperMethod, LanguageDriver languageDriver) { String providerMethodName; try { this.configuration = configuration; - this.sqlSourceParser = new SqlSourceBuilder(configuration); + this.languageDriver = languageDriver; this.providerType = (Class) provider.getClass().getMethod("type").invoke(provider); providerMethodName = (String) provider.getClass().getMethod("method").invoke(provider); @@ -126,7 +133,7 @@ private SqlSource createSqlSource(Object parameterObject) { + " using a specifying parameterObject. In this case, please specify a 'java.util.Map' object."); } Class parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); - return sqlSourceParser.parse(replacePlaceholder(sql), parameterType, new HashMap()); + return languageDriver.createSqlSource(configuration, sql, parameterType); } catch (BuilderException e) { throw e; } catch (Exception e) { diff --git a/src/test/java/org/apache/ibatis/submitted/sqlprovider/BaseMapper.java b/src/test/java/org/apache/ibatis/submitted/sqlprovider/BaseMapper.java index babebb472d8..7061a9fef9a 100644 --- a/src/test/java/org/apache/ibatis/submitted/sqlprovider/BaseMapper.java +++ b/src/test/java/org/apache/ibatis/submitted/sqlprovider/BaseMapper.java @@ -15,8 +15,12 @@ */ package org.apache.ibatis.submitted.sqlprovider; +import org.apache.ibatis.annotations.Lang; import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.InsertProvider; import org.apache.ibatis.annotations.SelectProvider; +import org.apache.ibatis.annotations.UpdateProvider; +import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -54,6 +58,16 @@ public interface BaseMapper { @SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByIdAndNameMultipleParamAndProviderContext") List selectActiveByIdAndName(Integer id, String name); + @Lang(XMLLanguageDriver.class) + @InsertProvider(type = OurSqlBuilder.class, method = "buildInsertSelective") + void insertSelective(T entity); + + @UpdateProvider(type= OurSqlBuilder.class, method= "buildUpdateSelective") + void updateSelective(T entity); + + @SelectProvider(type = OurSqlBuilder.class, method = "buildGetByEntityQuery") + List getByEntity(T entity); + @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface ContainsLogicalDelete { @@ -66,4 +80,10 @@ public interface BaseMapper { String tableName(); } + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface Column { + String value() default ""; + } + } diff --git a/src/test/java/org/apache/ibatis/submitted/sqlprovider/OurSqlBuilder.java b/src/test/java/org/apache/ibatis/submitted/sqlprovider/OurSqlBuilder.java index 359badc4fc3..30602094b5d 100644 --- a/src/test/java/org/apache/ibatis/submitted/sqlprovider/OurSqlBuilder.java +++ b/src/test/java/org/apache/ibatis/submitted/sqlprovider/OurSqlBuilder.java @@ -19,6 +19,11 @@ import org.apache.ibatis.builder.annotation.ProviderContext; import org.apache.ibatis.jdbc.SQL; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -213,4 +218,107 @@ public String buildSelectByIdAndNameMultipleParamAndProviderContext(final Intege }}.toString(); } + private Class getEntityClass(ProviderContext providerContext){ + Method mapperMethod = providerContext.getMapperMethod(); + Class declaringClass = mapperMethod.getDeclaringClass(); + Class mapperClass = providerContext.getMapperType(); + + Type[] types = mapperClass.getGenericInterfaces(); + for (Type type : types) { + if (type instanceof ParameterizedType) { + ParameterizedType t = (ParameterizedType) type; + if (t.getRawType() == declaringClass || mapperClass.isAssignableFrom((Class) t.getRawType())) { + Class returnType = (Class) t.getActualTypeArguments()[0]; + return returnType; + } + } + } + throw new RuntimeException("The interface [" + + mapperClass.getCanonicalName() + "] must specify a generic type."); + } + + private Map getColumnMap(ProviderContext context){ + Class entityClass = getEntityClass(context); + Field[] fields = entityClass.getDeclaredFields(); + Map columnMap = new LinkedHashMap(); + for (Field field : fields) { + BaseMapper.Column column = field.getAnnotation(BaseMapper.Column.class); + if(column != null){ + String columnName = column.value(); + if(columnName == null || columnName.length() == 0){ + columnName = field.getName(); + } + columnMap.put(columnName, field.getName()); + } + } + if(columnMap.size() == 0){ + throw new RuntimeException("There is no field in the class [" + + entityClass.getCanonicalName() + "] that specifies the @BaseMapper.Column annotation."); + } + return columnMap; + } + + public String buildInsertSelective(ProviderContext context) { + final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName(); + Map columnMap = getColumnMap(context); + StringBuffer sqlBuffer = new StringBuffer(); + sqlBuffer.append(""); + return sqlBuffer.toString(); + } + + public String buildUpdateSelective(ProviderContext context) { + final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName(); + Map columnMap = getColumnMap(context); + StringBuffer sqlBuffer = new StringBuffer(); + sqlBuffer.append(""); + return sqlBuffer.toString(); + } + + public String buildGetByEntityQuery(ProviderContext context) { + final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName(); + Map columnMap = getColumnMap(context); + StringBuffer sqlBuffer = new StringBuffer(); + sqlBuffer.append(""); + return sqlBuffer.toString(); + } + } diff --git a/src/test/java/org/apache/ibatis/submitted/sqlprovider/SqlProviderTest.java b/src/test/java/org/apache/ibatis/submitted/sqlprovider/SqlProviderTest.java index a85962f2a9c..41b5ef70ebc 100644 --- a/src/test/java/org/apache/ibatis/submitted/sqlprovider/SqlProviderTest.java +++ b/src/test/java/org/apache/ibatis/submitted/sqlprovider/SqlProviderTest.java @@ -538,4 +538,63 @@ public static String oneArgumentAndProviderContext(Integer value, ProviderContex } + @Test + public void shouldInsertUserSelective() { + SqlSession sqlSession = sqlSessionFactory.openSession(); + try { + Mapper mapper = sqlSession.getMapper(Mapper.class); + User user = new User(); + user.setId(999); + mapper.insertSelective(user); + + User loadedUser = mapper.getUser(999); + assertEquals(null, loadedUser.getName()); + + } finally { + sqlSession.close(); + } + } + + + @Test + public void shouldUpdateUserSelective() { + SqlSession sqlSession = sqlSessionFactory.openSession(); + try { + Mapper mapper = sqlSession.getMapper(Mapper.class); + User user = new User(); + user.setId(999); + user.setName("MyBatis"); + mapper.insert(user); + + user.setName(null); + mapper.updateSelective(user); + + User loadedUser = mapper.getUser(999); + assertEquals("MyBatis", loadedUser.getName()); + + } finally { + sqlSession.close(); + } + } + + @Test + public void mapperGetByEntity() { + SqlSession sqlSession = sqlSessionFactory.openSession(); + try { + Mapper mapper = sqlSession.getMapper(Mapper.class); + User query = new User(); + query.setName("User4"); + assertEquals(1, mapper.getByEntity(query).size()); + query = new User(); + query.setId(1); + assertEquals(1, mapper.getByEntity(query).size()); + query = new User(); + query.setId(1); + query.setName("User4"); + assertEquals(0, mapper.getByEntity(query).size()); + } finally { + sqlSession.close(); + } + } + } diff --git a/src/test/java/org/apache/ibatis/submitted/sqlprovider/User.java b/src/test/java/org/apache/ibatis/submitted/sqlprovider/User.java index 3b17cb33424..e44d259ba7c 100644 --- a/src/test/java/org/apache/ibatis/submitted/sqlprovider/User.java +++ b/src/test/java/org/apache/ibatis/submitted/sqlprovider/User.java @@ -16,8 +16,9 @@ package org.apache.ibatis.submitted.sqlprovider; public class User { - + @BaseMapper.Column private Integer id; + @BaseMapper.Column private String name; public Integer getId() { From e4b895f8d951af6c7b22b4587c0b059a1b19150d Mon Sep 17 00:00:00 2001 From: isea533 Date: Sat, 16 Mar 2019 22:26:51 +0800 Subject: [PATCH 2/8] Change StringBuffer to StringBuilder. --- .../apache/ibatis/submitted/sqlprovider/OurSqlBuilder.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/apache/ibatis/submitted/sqlprovider/OurSqlBuilder.java b/src/test/java/org/apache/ibatis/submitted/sqlprovider/OurSqlBuilder.java index 16e9bb5ccc3..00f06912823 100644 --- a/src/test/java/org/apache/ibatis/submitted/sqlprovider/OurSqlBuilder.java +++ b/src/test/java/org/apache/ibatis/submitted/sqlprovider/OurSqlBuilder.java @@ -262,7 +262,7 @@ private Map getColumnMap(ProviderContext context){ public String buildInsertSelective(ProviderContext context) { final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName(); Map columnMap = getColumnMap(context); - StringBuffer sqlBuffer = new StringBuffer(); + StringBuilder sqlBuffer = new StringBuilder(); sqlBuffer.append(""); return sqlBuffer.toString(); diff --git a/src/test/java/org/apache/ibatis/submitted/sqlprovider/User.java b/src/test/java/org/apache/ibatis/submitted/sqlprovider/User.java index e44d259ba7c..a3814bbde57 100644 --- a/src/test/java/org/apache/ibatis/submitted/sqlprovider/User.java +++ b/src/test/java/org/apache/ibatis/submitted/sqlprovider/User.java @@ -1,5 +1,5 @@ /** - * Copyright 2009-2015 the original author or authors. + * Copyright 2009-2019 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. From 36bcb2273114dce420b1ba3f2ec8fed8e141c54a Mon Sep 17 00:00:00 2001 From: Iwao AVE! Date: Wed, 20 Mar 2019 01:26:26 +0900 Subject: [PATCH 7/8] Refactoring: moved getLanguageDriver() from MapperBuilderAssistant to Configuration - In ProviderSqlSource, it is better not to assume the driver is already registered. - Replaced Class#newInstance() with Constructor#newInstance(). --- .../apache/ibatis/builder/MapperBuilderAssistant.java | 11 +++++------ .../builder/annotation/MapperAnnotationBuilder.java | 2 +- .../ibatis/builder/annotation/ProviderSqlSource.java | 4 +--- .../ibatis/builder/xml/XMLStatementBuilder.java | 2 +- .../ibatis/scripting/LanguageDriverRegistry.java | 6 +++--- .../java/org/apache/ibatis/session/Configuration.java | 8 ++++++++ 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/apache/ibatis/builder/MapperBuilderAssistant.java b/src/main/java/org/apache/ibatis/builder/MapperBuilderAssistant.java index 65fd186f6f8..04bf4b93441 100644 --- a/src/main/java/org/apache/ibatis/builder/MapperBuilderAssistant.java +++ b/src/main/java/org/apache/ibatis/builder/MapperBuilderAssistant.java @@ -457,13 +457,12 @@ public ResultMapping buildResultMapping(Class resultType, String property, St nestedResultMap, notNullColumn, columnPrefix, typeHandler, flags, null, null, configuration.isLazyLoadingEnabled()); } + /** + * @deprecated Use {@link Configuration#getLanguageDriver(Class)} + */ + @Deprecated public LanguageDriver getLanguageDriver(Class langClass) { - if (langClass != null) { - configuration.getLanguageRegistry().register(langClass); - } else { - langClass = configuration.getLanguageRegistry().getDefaultDriverClass(); - } - return configuration.getLanguageRegistry().getDriver(langClass); + return configuration.getLanguageDriver(langClass); } /** Backward compatibility signature. */ 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 b7db33db546..1c027155847 100644 --- a/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java +++ b/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java @@ -387,7 +387,7 @@ private LanguageDriver getLanguageDriver(Method method) { if (lang != null) { langClass = lang.value(); } - return assistant.getLanguageDriver(langClass); + return configuration.getLanguageDriver(langClass); } private Class getParameterType(Method method) { diff --git a/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java b/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java index 05285566759..e1e15968336 100644 --- a/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java +++ b/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java @@ -59,9 +59,7 @@ public ProviderSqlSource(Configuration configuration, Object provider, Class try { this.configuration = configuration; Lang lang = mapperMethod == null ? null : mapperMethod.getAnnotation(Lang.class); - this.languageDriver = lang == null ? - configuration.getLanguageRegistry().getDefaultDriver() : - configuration.getLanguageRegistry().getDriver(lang.value()); + this.languageDriver = configuration.getLanguageDriver(lang == null ? null : lang.value()); this.providerType = (Class) provider.getClass().getMethod("type").invoke(provider); providerMethodName = (String) provider.getClass().getMethod("method").invoke(provider); diff --git a/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java b/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java index 34d918245b3..5c178da280e 100644 --- a/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java +++ b/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java @@ -197,7 +197,7 @@ private LanguageDriver getLanguageDriver(String lang) { if (lang != null) { langClass = resolveClass(lang); } - return builderAssistant.getLanguageDriver(langClass); + return configuration.getLanguageDriver(langClass); } } diff --git a/src/main/java/org/apache/ibatis/scripting/LanguageDriverRegistry.java b/src/main/java/org/apache/ibatis/scripting/LanguageDriverRegistry.java index fd8a60a0c6e..f84d7b64928 100644 --- a/src/main/java/org/apache/ibatis/scripting/LanguageDriverRegistry.java +++ b/src/main/java/org/apache/ibatis/scripting/LanguageDriverRegistry.java @@ -31,13 +31,13 @@ public void register(Class cls) { if (cls == null) { throw new IllegalArgumentException("null is not a valid Language Driver"); } - if (!LANGUAGE_DRIVER_MAP.containsKey(cls)) { + LANGUAGE_DRIVER_MAP.computeIfAbsent(cls, k -> { try { - LANGUAGE_DRIVER_MAP.put(cls, cls.newInstance()); + return k.getDeclaredConstructor().newInstance(); } catch (Exception ex) { throw new ScriptingException("Failed to load language driver for " + cls.getName(), ex); } - } + }); } public void register(LanguageDriver instance) { diff --git a/src/main/java/org/apache/ibatis/session/Configuration.java b/src/main/java/org/apache/ibatis/session/Configuration.java index 3ba3ee42c47..7454e8629f2 100644 --- a/src/main/java/org/apache/ibatis/session/Configuration.java +++ b/src/main/java/org/apache/ibatis/session/Configuration.java @@ -536,6 +536,14 @@ public LanguageDriver getDefaultScriptingLanguageInstance() { return languageRegistry.getDefaultDriver(); } + public LanguageDriver getLanguageDriver(Class langClass) { + if (langClass == null) { + return languageRegistry.getDefaultDriver(); + } + languageRegistry.register(langClass); + return languageRegistry.getDriver(langClass); + } + /** * @deprecated Use {@link #getDefaultScriptingLanguageInstance()} */ From 88514e641d42d68817897ec6eaa370c6a78ef275 Mon Sep 17 00:00:00 2001 From: Iwao AVE! Date: Wed, 20 Mar 2019 03:06:53 +0900 Subject: [PATCH 8/8] Removed unused method, added @since --- .../apache/ibatis/builder/annotation/ProviderSqlSource.java | 5 ----- src/main/java/org/apache/ibatis/session/Configuration.java | 3 +++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java b/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java index e1e15968336..48d2a697276 100644 --- a/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java +++ b/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java @@ -23,7 +23,6 @@ import org.apache.ibatis.builder.BuilderException; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.SqlSource; -import org.apache.ibatis.parsing.PropertyParser; import org.apache.ibatis.reflection.ParamNameResolver; import org.apache.ibatis.scripting.LanguageDriver; import org.apache.ibatis.session.Configuration; @@ -169,8 +168,4 @@ private String invokeProviderMethod(Object... args) throws Exception { return sql != null ? sql.toString() : null; } - private String replacePlaceholder(String sql) { - return PropertyParser.parse(sql, configuration.getVariables()); - } - } diff --git a/src/main/java/org/apache/ibatis/session/Configuration.java b/src/main/java/org/apache/ibatis/session/Configuration.java index 7454e8629f2..ff54eabf0d8 100644 --- a/src/main/java/org/apache/ibatis/session/Configuration.java +++ b/src/main/java/org/apache/ibatis/session/Configuration.java @@ -536,6 +536,9 @@ public LanguageDriver getDefaultScriptingLanguageInstance() { return languageRegistry.getDefaultDriver(); } + /** + * @since 3.5.1 + */ public LanguageDriver getLanguageDriver(Class langClass) { if (langClass == null) { return languageRegistry.getDefaultDriver();