Skip to content

Commit 737fc80

Browse files
committed
Enhanced ProviderSqlSource to support dynamic sqlSource.
1 parent 9233556 commit 737fc80

File tree

10 files changed

+222
-8
lines changed

10 files changed

+222
-8
lines changed

src/main/java/org/apache/ibatis/annotations/DeleteProvider.java

+2
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,6 @@
3131
Class<?> type();
3232

3333
String method();
34+
35+
boolean cacheSqlSource() default false;
3436
}

src/main/java/org/apache/ibatis/annotations/InsertProvider.java

+2
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,6 @@
3131
Class<?> type();
3232

3333
String method();
34+
35+
boolean cacheSqlSource() default false;
3436
}

src/main/java/org/apache/ibatis/annotations/SelectProvider.java

+2
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,6 @@
3131
Class<?> type();
3232

3333
String method();
34+
35+
boolean cacheSqlSource() default false;
3436
}

src/main/java/org/apache/ibatis/annotations/UpdateProvider.java

+2
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,6 @@
3131
Class<?> type();
3232

3333
String method();
34+
35+
boolean cacheSqlSource() default false;
3436
}

src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterT
465465
return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
466466
} else if (sqlProviderAnnotationType != null) {
467467
Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
468-
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
468+
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method, languageDriver);
469469
}
470470
return null;
471471
} catch (Exception e) {

src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java

+24-6
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@
1616
package org.apache.ibatis.builder.annotation;
1717

1818
import java.lang.reflect.Method;
19-
import java.util.HashMap;
2019
import java.util.Map;
2120

2221
import org.apache.ibatis.builder.BuilderException;
23-
import org.apache.ibatis.builder.SqlSourceBuilder;
2422
import org.apache.ibatis.mapping.BoundSql;
2523
import org.apache.ibatis.mapping.SqlSource;
2624
import org.apache.ibatis.parsing.PropertyParser;
2725
import org.apache.ibatis.reflection.ParamNameResolver;
26+
import org.apache.ibatis.scripting.LanguageDriver;
27+
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
2828
import org.apache.ibatis.session.Configuration;
2929

3030
/**
@@ -34,13 +34,15 @@
3434
public class ProviderSqlSource implements SqlSource {
3535

3636
private final Configuration configuration;
37-
private final SqlSourceBuilder sqlSourceParser;
3837
private final Class<?> providerType;
38+
private final LanguageDriver languageDriver;
39+
private final boolean cacheSqlSource;
3940
private Method providerMethod;
4041
private String[] providerMethodArgumentNames;
4142
private Class<?>[] providerMethodParameterTypes;
4243
private ProviderContext providerContext;
4344
private Integer providerContextIndex;
45+
private SqlSource sqlSource;
4446

4547
/**
4648
* @deprecated Please use the {@link #ProviderSqlSource(Configuration, Object, Class, Method)} instead of this.
@@ -54,11 +56,19 @@ public ProviderSqlSource(Configuration configuration, Object provider) {
5456
* @since 3.4.5
5557
*/
5658
public ProviderSqlSource(Configuration configuration, Object provider, Class<?> mapperType, Method mapperMethod) {
59+
this(configuration, provider, mapperType, mapperMethod, new XMLLanguageDriver());
60+
}
61+
62+
/**
63+
* @since 3.4.6
64+
*/
65+
public ProviderSqlSource(Configuration configuration, Object provider, Class<?> mapperType, Method mapperMethod, LanguageDriver languageDriver) {
5766
String providerMethodName;
5867
try {
5968
this.configuration = configuration;
60-
this.sqlSourceParser = new SqlSourceBuilder(configuration);
69+
this.languageDriver = languageDriver;
6170
this.providerType = (Class<?>) provider.getClass().getMethod("type").invoke(provider);
71+
this.cacheSqlSource = (Boolean) provider.getClass().getMethod("cacheSqlSource").invoke(provider);
6272
providerMethodName = (String) provider.getClass().getMethod("method").invoke(provider);
6373

6474
for (Method m : this.providerType.getMethods()) {
@@ -100,7 +110,15 @@ public ProviderSqlSource(Configuration configuration, Object provider, Class<?>
100110

101111
@Override
102112
public BoundSql getBoundSql(Object parameterObject) {
103-
SqlSource sqlSource = createSqlSource(parameterObject);
113+
SqlSource sqlSource;
114+
if (this.sqlSource != null) {
115+
sqlSource = this.sqlSource;
116+
} else {
117+
sqlSource = createSqlSource(parameterObject);
118+
if(this.cacheSqlSource){
119+
this.sqlSource = sqlSource;
120+
}
121+
}
104122
return sqlSource.getBoundSql(parameterObject);
105123
}
106124

@@ -127,7 +145,7 @@ private SqlSource createSqlSource(Object parameterObject) {
127145
+ " using a specifying parameterObject. In this case, please specify a 'java.util.Map' object.");
128146
}
129147
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
130-
return sqlSourceParser.parse(replacePlaceholder(sql), parameterType, new HashMap<String, Object>());
148+
return languageDriver.createSqlSource(configuration, sql, parameterType);
131149
} catch (BuilderException e) {
132150
throw e;
133151
} catch (Exception e) {

src/test/java/org/apache/ibatis/submitted/sqlprovider/BaseMapper.java

+20
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@
1515
*/
1616
package org.apache.ibatis.submitted.sqlprovider;
1717

18+
import org.apache.ibatis.annotations.Lang;
1819
import org.apache.ibatis.annotations.Param;
20+
import org.apache.ibatis.annotations.InsertProvider;
1921
import org.apache.ibatis.annotations.SelectProvider;
22+
import org.apache.ibatis.annotations.UpdateProvider;
23+
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
2024

2125
import java.lang.annotation.ElementType;
2226
import java.lang.annotation.Retention;
@@ -54,6 +58,16 @@ public interface BaseMapper<T> {
5458
@SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByIdAndNameMultipleParamAndProviderContext")
5559
List<T> selectActiveByIdAndName(Integer id, String name);
5660

61+
@Lang(XMLLanguageDriver.class)
62+
@InsertProvider(type = OurSqlBuilder.class, method = "buildInsertSelective", cacheSqlSource = true)
63+
void insertSelective(T entity);
64+
65+
@UpdateProvider(type= OurSqlBuilder.class, method= "buildUpdateSelective", cacheSqlSource = true)
66+
void updateSelective(T entity);
67+
68+
@SelectProvider(type = OurSqlBuilder.class, method = "buildGetByEntityQuery", cacheSqlSource = true)
69+
List<T> getByEntity(T entity);
70+
5771
@Retention(RetentionPolicy.RUNTIME)
5872
@Target(ElementType.METHOD)
5973
@interface ContainsLogicalDelete {
@@ -66,4 +80,10 @@ public interface BaseMapper<T> {
6680
String tableName();
6781
}
6882

83+
@Retention(RetentionPolicy.RUNTIME)
84+
@Target(ElementType.FIELD)
85+
@interface Column {
86+
String value() default "";
87+
}
88+
6989
}

src/test/java/org/apache/ibatis/submitted/sqlprovider/OurSqlBuilder.java

+108
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
import org.apache.ibatis.builder.annotation.ProviderContext;
2020
import org.apache.ibatis.jdbc.SQL;
2121

22+
import java.lang.reflect.Field;
23+
import java.lang.reflect.Method;
24+
import java.lang.reflect.ParameterizedType;
25+
import java.lang.reflect.Type;
26+
import java.util.LinkedHashMap;
2227
import java.util.List;
2328
import java.util.Map;
2429

@@ -213,4 +218,107 @@ public String buildSelectByIdAndNameMultipleParamAndProviderContext(final Intege
213218
}}.toString();
214219
}
215220

221+
private Class getEntityClass(ProviderContext providerContext){
222+
Method mapperMethod = providerContext.getMapperMethod();
223+
Class<?> declaringClass = mapperMethod.getDeclaringClass();
224+
Class<?> mapperClass = providerContext.getMapperType();
225+
226+
Type[] types = mapperClass.getGenericInterfaces();
227+
for (Type type : types) {
228+
if (type instanceof ParameterizedType) {
229+
ParameterizedType t = (ParameterizedType) type;
230+
if (t.getRawType() == declaringClass || mapperClass.isAssignableFrom((Class<?>) t.getRawType())) {
231+
Class<?> returnType = (Class<?>) t.getActualTypeArguments()[0];
232+
return returnType;
233+
}
234+
}
235+
}
236+
throw new RuntimeException("The interface ["
237+
+ mapperClass.getCanonicalName() + "] must specify a generic type.");
238+
}
239+
240+
private Map<String, String> getColumnMap(ProviderContext context){
241+
Class entityClass = getEntityClass(context);
242+
Field[] fields = entityClass.getDeclaredFields();
243+
Map<String, String> columnMap = new LinkedHashMap<String, String>();
244+
for (Field field : fields) {
245+
BaseMapper.Column column = field.getAnnotation(BaseMapper.Column.class);
246+
if(column != null){
247+
String columnName = column.value();
248+
if(columnName == null || columnName.length() == 0){
249+
columnName = field.getName();
250+
}
251+
columnMap.put(columnName, field.getName());
252+
}
253+
}
254+
if(columnMap.size() == 0){
255+
throw new RuntimeException("There is no field in the class ["
256+
+ entityClass.getCanonicalName() + "] that specifies the @BaseMapper.Column annotation.");
257+
}
258+
return columnMap;
259+
}
260+
261+
public String buildInsertSelective(ProviderContext context) {
262+
final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName();
263+
Map<String, String> columnMap = getColumnMap(context);
264+
StringBuffer sqlBuffer = new StringBuffer();
265+
sqlBuffer.append("<script>");
266+
sqlBuffer.append("insert into ");
267+
sqlBuffer.append(tableName);
268+
sqlBuffer.append("<trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">");
269+
for (Map.Entry<String, String> entry : columnMap.entrySet()) {
270+
sqlBuffer.append("<if test=\"").append(entry.getValue()).append(" != null\">");
271+
sqlBuffer.append(entry.getKey()).append(",");
272+
sqlBuffer.append("</if>");
273+
}
274+
sqlBuffer.append("</trim>");
275+
sqlBuffer.append("<trim prefix=\"VALUES (\" suffix=\")\" suffixOverrides=\",\">");
276+
for (String field : columnMap.values()) {
277+
sqlBuffer.append("<if test=\"").append(field).append(" != null\">");
278+
sqlBuffer.append("#{").append(field).append("} ,");
279+
sqlBuffer.append("</if>");
280+
}
281+
sqlBuffer.append("</trim>");
282+
sqlBuffer.append("</script>");
283+
return sqlBuffer.toString();
284+
}
285+
286+
public String buildUpdateSelective(ProviderContext context) {
287+
final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName();
288+
Map<String, String> columnMap = getColumnMap(context);
289+
StringBuffer sqlBuffer = new StringBuffer();
290+
sqlBuffer.append("<script>");
291+
sqlBuffer.append("update ");
292+
sqlBuffer.append(tableName);
293+
sqlBuffer.append("<set>");
294+
for (Map.Entry<String, String> entry : columnMap.entrySet()) {
295+
sqlBuffer.append("<if test=\"").append(entry.getValue()).append(" != null\">");
296+
sqlBuffer.append(entry.getKey()).append(" = #{").append(entry.getValue()).append("} ,");
297+
sqlBuffer.append("</if>");
298+
}
299+
sqlBuffer.append("</set>");
300+
//For simplicity, there is no @Id annotation here, using default id directly
301+
sqlBuffer.append("where id = #{id}");
302+
sqlBuffer.append("</script>");
303+
return sqlBuffer.toString();
304+
}
305+
306+
public String buildGetByEntityQuery(ProviderContext context) {
307+
final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName();
308+
Map<String, String> columnMap = getColumnMap(context);
309+
StringBuffer sqlBuffer = new StringBuffer();
310+
sqlBuffer.append("<script>");
311+
sqlBuffer.append("select * from ");
312+
sqlBuffer.append(tableName);
313+
sqlBuffer.append("<where>");
314+
for (Map.Entry<String, String> entry : columnMap.entrySet()) {
315+
sqlBuffer.append("<if test=\"").append(entry.getValue()).append(" != null\">");
316+
sqlBuffer.append("and ").append(entry.getKey()).append(" = #{").append(entry.getValue()).append("}");
317+
sqlBuffer.append("</if>");
318+
}
319+
sqlBuffer.append("</where>");
320+
sqlBuffer.append("</script>");
321+
return sqlBuffer.toString();
322+
}
323+
216324
}

src/test/java/org/apache/ibatis/submitted/sqlprovider/SqlProviderTest.java

+59
Original file line numberDiff line numberDiff line change
@@ -508,4 +508,63 @@ public String multipleProviderContext(ProviderContext providerContext1, Provider
508508
}
509509
}
510510

511+
@Test
512+
public void shouldInsertUserSelective() {
513+
SqlSession sqlSession = sqlSessionFactory.openSession();
514+
try {
515+
Mapper mapper = sqlSession.getMapper(Mapper.class);
516+
User user = new User();
517+
user.setId(999);
518+
mapper.insertSelective(user);
519+
520+
User loadedUser = mapper.getUser(999);
521+
assertEquals(null, loadedUser.getName());
522+
523+
} finally {
524+
sqlSession.close();
525+
}
526+
}
527+
528+
529+
@Test
530+
public void shouldUpdateUserSelective() {
531+
SqlSession sqlSession = sqlSessionFactory.openSession();
532+
try {
533+
Mapper mapper = sqlSession.getMapper(Mapper.class);
534+
User user = new User();
535+
user.setId(999);
536+
user.setName("MyBatis");
537+
mapper.insert(user);
538+
539+
user.setName(null);
540+
mapper.updateSelective(user);
541+
542+
User loadedUser = mapper.getUser(999);
543+
assertEquals("MyBatis", loadedUser.getName());
544+
545+
} finally {
546+
sqlSession.close();
547+
}
548+
}
549+
550+
@Test
551+
public void mapperGetByEntity() {
552+
SqlSession sqlSession = sqlSessionFactory.openSession();
553+
try {
554+
Mapper mapper = sqlSession.getMapper(Mapper.class);
555+
User query = new User();
556+
query.setName("User4");
557+
assertEquals(1, mapper.getByEntity(query).size());
558+
query = new User();
559+
query.setId(1);
560+
assertEquals(1, mapper.getByEntity(query).size());
561+
query = new User();
562+
query.setId(1);
563+
query.setName("User4");
564+
assertEquals(0, mapper.getByEntity(query).size());
565+
} finally {
566+
sqlSession.close();
567+
}
568+
}
569+
511570
}

src/test/java/org/apache/ibatis/submitted/sqlprovider/User.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
package org.apache.ibatis.submitted.sqlprovider;
1717

1818
public class User {
19-
19+
@BaseMapper.Column
2020
private Integer id;
21+
@BaseMapper.Column
2122
private String name;
2223

2324
public Integer getId() {

0 commit comments

Comments
 (0)