From bcfe4801ea63e2ae10dc1df0675c842084c9eeee Mon Sep 17 00:00:00 2001 From: isea533 Date: Sun, 8 Oct 2017 21:49:33 +0800 Subject: [PATCH] Enhanced ProviderSqlSource to support dynamic sqlSource. --- .../ibatis/annotations/DeleteProvider.java | 6 + .../ibatis/annotations/InsertProvider.java | 6 + .../ibatis/annotations/SelectProvider.java | 6 + .../ibatis/annotations/UpdateProvider.java | 6 + .../annotation/MapperAnnotationBuilder.java | 2 +- .../builder/annotation/ProviderSqlSource.java | 35 +++++- .../submitted/sqlprovider/BaseMapper.java | 20 ++++ .../submitted/sqlprovider/OurSqlBuilder.java | 108 ++++++++++++++++++ .../sqlprovider/SqlProviderTest.java | 59 ++++++++++ .../ibatis/submitted/sqlprovider/User.java | 3 +- 10 files changed, 243 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/apache/ibatis/annotations/DeleteProvider.java b/src/main/java/org/apache/ibatis/annotations/DeleteProvider.java index fb315fc0aa7..4fe9664f662 100644 --- a/src/main/java/org/apache/ibatis/annotations/DeleteProvider.java +++ b/src/main/java/org/apache/ibatis/annotations/DeleteProvider.java @@ -31,4 +31,10 @@ Class type(); String method(); + + /** + * Cache the generated SqlSource, avoiding every rebuild. + * @since 3.4.6 + */ + boolean cacheSqlSource() default false; } diff --git a/src/main/java/org/apache/ibatis/annotations/InsertProvider.java b/src/main/java/org/apache/ibatis/annotations/InsertProvider.java index bb38bcdc6fc..1eb3566d2e0 100644 --- a/src/main/java/org/apache/ibatis/annotations/InsertProvider.java +++ b/src/main/java/org/apache/ibatis/annotations/InsertProvider.java @@ -31,4 +31,10 @@ Class type(); String method(); + + /** + * Cache the generated SqlSource, avoiding every rebuild. + * @since 3.4.6 + */ + boolean cacheSqlSource() default false; } diff --git a/src/main/java/org/apache/ibatis/annotations/SelectProvider.java b/src/main/java/org/apache/ibatis/annotations/SelectProvider.java index 68e95e566e4..6ec3fcff138 100644 --- a/src/main/java/org/apache/ibatis/annotations/SelectProvider.java +++ b/src/main/java/org/apache/ibatis/annotations/SelectProvider.java @@ -31,4 +31,10 @@ Class type(); String method(); + + /** + * Cache the generated SqlSource, avoiding every rebuild. + * @since 3.4.6 + */ + boolean cacheSqlSource() default false; } diff --git a/src/main/java/org/apache/ibatis/annotations/UpdateProvider.java b/src/main/java/org/apache/ibatis/annotations/UpdateProvider.java index 695bfa7819b..584ba81523c 100644 --- a/src/main/java/org/apache/ibatis/annotations/UpdateProvider.java +++ b/src/main/java/org/apache/ibatis/annotations/UpdateProvider.java @@ -31,4 +31,10 @@ Class type(); String method(); + + /** + * Cache the generated SqlSource, avoiding every rebuild. + * @since 3.4.6 + */ + boolean cacheSqlSource() default false; } 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 3434126d341..dd1c1a91c24 100644 --- a/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java +++ b/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java @@ -465,7 +465,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 325da40e8da..b929ebb3f10 100644 --- a/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java +++ b/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java @@ -16,15 +16,15 @@ package org.apache.ibatis.builder.annotation; import java.lang.reflect.Method; -import java.util.HashMap; 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.scripting.xmltags.XMLLanguageDriver; import org.apache.ibatis.session.Configuration; /** @@ -34,13 +34,15 @@ public class ProviderSqlSource implements SqlSource { private final Configuration configuration; - private final SqlSourceBuilder sqlSourceParser; private final Class providerType; + private final LanguageDriver languageDriver; + private final boolean cacheSqlSource; private Method providerMethod; private String[] providerMethodArgumentNames; private Class[] providerMethodParameterTypes; private ProviderContext providerContext; private Integer providerContextIndex; + private SqlSource sqlSource; /** * @deprecated Please use the {@link #ProviderSqlSource(Configuration, Object, Class, Method)} instead of this. @@ -54,11 +56,24 @@ 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); + //Compatible with velocity and freemarker LanguageDriver + if(languageDriver instanceof XMLLanguageDriver){ + this.languageDriver = languageDriver; + } else { + this.languageDriver = new XMLLanguageDriver(); + } this.providerType = (Class) provider.getClass().getMethod("type").invoke(provider); + this.cacheSqlSource = (Boolean) provider.getClass().getMethod("cacheSqlSource").invoke(provider); providerMethodName = (String) provider.getClass().getMethod("method").invoke(provider); for (Method m : this.providerType.getMethods()) { @@ -100,7 +115,15 @@ public ProviderSqlSource(Configuration configuration, Object provider, Class @Override public BoundSql getBoundSql(Object parameterObject) { - SqlSource sqlSource = createSqlSource(parameterObject); + SqlSource sqlSource; + if (this.sqlSource != null) { + sqlSource = this.sqlSource; + } else { + sqlSource = createSqlSource(parameterObject); + if(this.cacheSqlSource){ + this.sqlSource = sqlSource; + } + } return sqlSource.getBoundSql(parameterObject); } @@ -127,7 +150,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..84d4c81586a 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", cacheSqlSource = true) + void insertSelective(T entity); + + @UpdateProvider(type= OurSqlBuilder.class, method= "buildUpdateSelective", cacheSqlSource = true) + void updateSelective(T entity); + + @SelectProvider(type = OurSqlBuilder.class, method = "buildGetByEntityQuery", cacheSqlSource = true) + 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 f28434422b2..fc10370d226 100644 --- a/src/test/java/org/apache/ibatis/submitted/sqlprovider/SqlProviderTest.java +++ b/src/test/java/org/apache/ibatis/submitted/sqlprovider/SqlProviderTest.java @@ -508,4 +508,63 @@ public String multipleProviderContext(ProviderContext providerContext1, Provider } } + @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() {