Skip to content

Commit f738839

Browse files
authored
Merge pull request #1391 from abel533/driver
Add LanguageDriver to ProviderSqlSource
2 parents 76826c8 + 88514e6 commit f738839

File tree

10 files changed

+218
-25
lines changed

10 files changed

+218
-25
lines changed

src/main/java/org/apache/ibatis/builder/MapperBuilderAssistant.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -457,13 +457,12 @@ public ResultMapping buildResultMapping(Class<?> resultType, String property, St
457457
nestedResultMap, notNullColumn, columnPrefix, typeHandler, flags, null, null, configuration.isLazyLoadingEnabled());
458458
}
459459

460+
/**
461+
* @deprecated Use {@link Configuration#getLanguageDriver(Class)}
462+
*/
463+
@Deprecated
460464
public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) {
461-
if (langClass != null) {
462-
configuration.getLanguageRegistry().register(langClass);
463-
} else {
464-
langClass = configuration.getLanguageRegistry().getDefaultDriverClass();
465-
}
466-
return configuration.getLanguageRegistry().getDriver(langClass);
465+
return configuration.getLanguageDriver(langClass);
467466
}
468467

469468
/** Backward compatibility signature. */

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ private LanguageDriver getLanguageDriver(Method method) {
387387
if (lang != null) {
388388
langClass = lang.value();
389389
}
390-
return assistant.getLanguageDriver(langClass);
390+
return configuration.getLanguageDriver(langClass);
391391
}
392392

393393
private Class<?> getParameterType(Method method) {

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

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@
1717

1818
import java.lang.reflect.Method;
1919
import java.lang.reflect.Modifier;
20-
import java.util.HashMap;
2120
import java.util.Map;
2221

22+
import org.apache.ibatis.annotations.Lang;
2323
import org.apache.ibatis.builder.BuilderException;
24-
import org.apache.ibatis.builder.SqlSourceBuilder;
2524
import org.apache.ibatis.mapping.BoundSql;
2625
import org.apache.ibatis.mapping.SqlSource;
27-
import org.apache.ibatis.parsing.PropertyParser;
2826
import org.apache.ibatis.reflection.ParamNameResolver;
27+
import org.apache.ibatis.scripting.LanguageDriver;
2928
import org.apache.ibatis.session.Configuration;
3029

3130
/**
@@ -35,8 +34,8 @@
3534
public class ProviderSqlSource implements SqlSource {
3635

3736
private final Configuration configuration;
38-
private final SqlSourceBuilder sqlSourceParser;
3937
private final Class<?> providerType;
38+
private final LanguageDriver languageDriver;
4039
private Method providerMethod;
4140
private String[] providerMethodArgumentNames;
4241
private Class<?>[] providerMethodParameterTypes;
@@ -58,7 +57,8 @@ public ProviderSqlSource(Configuration configuration, Object provider, Class<?>
5857
String providerMethodName;
5958
try {
6059
this.configuration = configuration;
61-
this.sqlSourceParser = new SqlSourceBuilder(configuration);
60+
Lang lang = mapperMethod == null ? null : mapperMethod.getAnnotation(Lang.class);
61+
this.languageDriver = configuration.getLanguageDriver(lang == null ? null : lang.value());
6262
this.providerType = (Class<?>) provider.getClass().getMethod("type").invoke(provider);
6363
providerMethodName = (String) provider.getClass().getMethod("method").invoke(provider);
6464

@@ -126,7 +126,7 @@ private SqlSource createSqlSource(Object parameterObject) {
126126
+ " using a specifying parameterObject. In this case, please specify a 'java.util.Map' object.");
127127
}
128128
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
129-
return sqlSourceParser.parse(replacePlaceholder(sql), parameterType, new HashMap<>());
129+
return languageDriver.createSqlSource(configuration, sql, parameterType);
130130
} catch (BuilderException e) {
131131
throw e;
132132
} catch (Exception e) {
@@ -168,8 +168,4 @@ private String invokeProviderMethod(Object... args) throws Exception {
168168
return sql != null ? sql.toString() : null;
169169
}
170170

171-
private String replacePlaceholder(String sql) {
172-
return PropertyParser.parse(sql, configuration.getVariables());
173-
}
174-
175171
}

src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ private LanguageDriver getLanguageDriver(String lang) {
197197
if (lang != null) {
198198
langClass = resolveClass(lang);
199199
}
200-
return builderAssistant.getLanguageDriver(langClass);
200+
return configuration.getLanguageDriver(langClass);
201201
}
202202

203203
}

src/main/java/org/apache/ibatis/scripting/LanguageDriverRegistry.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ public void register(Class<? extends LanguageDriver> cls) {
3131
if (cls == null) {
3232
throw new IllegalArgumentException("null is not a valid Language Driver");
3333
}
34-
if (!LANGUAGE_DRIVER_MAP.containsKey(cls)) {
34+
LANGUAGE_DRIVER_MAP.computeIfAbsent(cls, k -> {
3535
try {
36-
LANGUAGE_DRIVER_MAP.put(cls, cls.newInstance());
36+
return k.getDeclaredConstructor().newInstance();
3737
} catch (Exception ex) {
3838
throw new ScriptingException("Failed to load language driver for " + cls.getName(), ex);
3939
}
40-
}
40+
});
4141
}
4242

4343
public void register(LanguageDriver instance) {

src/main/java/org/apache/ibatis/session/Configuration.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,17 @@ public LanguageDriver getDefaultScriptingLanguageInstance() {
536536
return languageRegistry.getDefaultDriver();
537537
}
538538

539+
/**
540+
* @since 3.5.1
541+
*/
542+
public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) {
543+
if (langClass == null) {
544+
return languageRegistry.getDefaultDriver();
545+
}
546+
languageRegistry.register(langClass);
547+
return languageRegistry.getDriver(langClass);
548+
}
549+
539550
/**
540551
* @deprecated Use {@link #getDefaultScriptingLanguageInstance()}
541552
*/

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2009-2018 the original author or authors.
2+
* Copyright 2009-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -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")
63+
void insertSelective(T entity);
64+
65+
@UpdateProvider(type= OurSqlBuilder.class, method= "buildUpdateSelective")
66+
void updateSelective(T entity);
67+
68+
@SelectProvider(type = OurSqlBuilder.class, method = "buildGetByEntityQuery")
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

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2009-2018 the original author or authors.
2+
* Copyright 2009-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -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

@@ -214,4 +219,106 @@ public String buildSelectByIdAndNameMultipleParamAndProviderContext(final Intege
214219
}}.toString();
215220
}
216221

222+
private Class<?> getEntityClass(ProviderContext providerContext) {
223+
Method mapperMethod = providerContext.getMapperMethod();
224+
Class<?> declaringClass = mapperMethod.getDeclaringClass();
225+
Class<?> mapperClass = providerContext.getMapperType();
226+
227+
Type[] types = mapperClass.getGenericInterfaces();
228+
for (Type type : types) {
229+
if (type instanceof ParameterizedType) {
230+
ParameterizedType t = (ParameterizedType) type;
231+
if (t.getRawType() == declaringClass || mapperClass.isAssignableFrom((Class<?>) t.getRawType())) {
232+
Class<?> returnType = (Class<?>) t.getActualTypeArguments()[0];
233+
return returnType;
234+
}
235+
}
236+
}
237+
throw new RuntimeException("The interface [" + 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 [" + entityClass.getCanonicalName()
256+
+ "] 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+
StringBuilder sqlBuffer = new StringBuilder();
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+
StringBuilder sqlBuffer = new StringBuilder();
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+
StringBuilder sqlBuffer = new StringBuilder();
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+
217324
}

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

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,4 +538,63 @@ public static String oneArgumentAndProviderContext(Integer value, ProviderContex
538538

539539
}
540540

541+
@Test
542+
public void shouldInsertUserSelective() {
543+
SqlSession sqlSession = sqlSessionFactory.openSession();
544+
try {
545+
Mapper mapper = sqlSession.getMapper(Mapper.class);
546+
User user = new User();
547+
user.setId(999);
548+
mapper.insertSelective(user);
549+
550+
User loadedUser = mapper.getUser(999);
551+
assertEquals(null, loadedUser.getName());
552+
553+
} finally {
554+
sqlSession.close();
555+
}
556+
}
557+
558+
559+
@Test
560+
public void shouldUpdateUserSelective() {
561+
SqlSession sqlSession = sqlSessionFactory.openSession();
562+
try {
563+
Mapper mapper = sqlSession.getMapper(Mapper.class);
564+
User user = new User();
565+
user.setId(999);
566+
user.setName("MyBatis");
567+
mapper.insert(user);
568+
569+
user.setName(null);
570+
mapper.updateSelective(user);
571+
572+
User loadedUser = mapper.getUser(999);
573+
assertEquals("MyBatis", loadedUser.getName());
574+
575+
} finally {
576+
sqlSession.close();
577+
}
578+
}
579+
580+
@Test
581+
public void mapperGetByEntity() {
582+
SqlSession sqlSession = sqlSessionFactory.openSession();
583+
try {
584+
Mapper mapper = sqlSession.getMapper(Mapper.class);
585+
User query = new User();
586+
query.setName("User4");
587+
assertEquals(1, mapper.getByEntity(query).size());
588+
query = new User();
589+
query.setId(1);
590+
assertEquals(1, mapper.getByEntity(query).size());
591+
query = new User();
592+
query.setId(1);
593+
query.setName("User4");
594+
assertEquals(0, mapper.getByEntity(query).size());
595+
} finally {
596+
sqlSession.close();
597+
}
598+
}
599+
541600
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2009-2015 the original author or authors.
2+
* Copyright 2009-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -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)