Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LanguageDriver to ProviderSqlSource #1391

Merged
merged 9 commits into from Mar 19, 2019
Expand Up @@ -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<? extends LanguageDriver> 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. */
Expand Down
Expand Up @@ -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) {
Expand Down
Expand Up @@ -17,15 +17,14 @@

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

import org.apache.ibatis.annotations.Lang;
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;

/**
Expand All @@ -35,8 +34,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;
Expand All @@ -58,7 +57,8 @@ public ProviderSqlSource(Configuration configuration, Object provider, Class<?>
String providerMethodName;
try {
this.configuration = configuration;
this.sqlSourceParser = new SqlSourceBuilder(configuration);
Lang lang = mapperMethod == null ? null : mapperMethod.getAnnotation(Lang.class);
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);

Expand Down Expand Up @@ -126,7 +126,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<>());
kazuki43zoo marked this conversation as resolved.
Show resolved Hide resolved
return languageDriver.createSqlSource(configuration, sql, parameterType);
} catch (BuilderException e) {
throw e;
} catch (Exception e) {
Expand Down Expand Up @@ -168,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());
}

}
Expand Up @@ -197,7 +197,7 @@ private LanguageDriver getLanguageDriver(String lang) {
if (lang != null) {
langClass = resolveClass(lang);
}
return builderAssistant.getLanguageDriver(langClass);
return configuration.getLanguageDriver(langClass);
}

}
Expand Up @@ -31,13 +31,13 @@ public void register(Class<? extends LanguageDriver> 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) {
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/org/apache/ibatis/session/Configuration.java
Expand Up @@ -536,6 +536,17 @@ public LanguageDriver getDefaultScriptingLanguageInstance() {
return languageRegistry.getDefaultDriver();
}

/**
* @since 3.5.1
*/
public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) {
kazuki43zoo marked this conversation as resolved.
Show resolved Hide resolved
if (langClass == null) {
return languageRegistry.getDefaultDriver();
}
languageRegistry.register(langClass);
return languageRegistry.getDriver(langClass);
}

/**
* @deprecated Use {@link #getDefaultScriptingLanguageInstance()}
*/
Expand Down
@@ -1,5 +1,5 @@
/**
* Copyright 2009-2018 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.
Expand All @@ -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;
Expand Down Expand Up @@ -54,6 +58,16 @@ public interface BaseMapper<T> {
@SelectProvider(type = OurSqlBuilder.class, method = "buildSelectByIdAndNameMultipleParamAndProviderContext")
List<T> 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<T> getByEntity(T entity);

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface ContainsLogicalDelete {
Expand All @@ -66,4 +80,10 @@ public interface BaseMapper<T> {
String tableName();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Column {
String value() default "";
}

}
@@ -1,5 +1,5 @@
/**
* Copyright 2009-2018 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.
Expand All @@ -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;

Expand Down Expand Up @@ -214,4 +219,106 @@ 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<String, String> getColumnMap(ProviderContext context) {
Class<?> entityClass = getEntityClass(context);
Field[] fields = entityClass.getDeclaredFields();
Map<String, String> columnMap = new LinkedHashMap<String, String>();
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<String, String> columnMap = getColumnMap(context);
StringBuilder sqlBuffer = new StringBuilder();
sqlBuffer.append("<script>");
sqlBuffer.append("insert into ");
sqlBuffer.append(tableName);
sqlBuffer.append("<trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">");
for (Map.Entry<String, String> entry : columnMap.entrySet()) {
sqlBuffer.append("<if test=\"").append(entry.getValue()).append(" != null\">");
sqlBuffer.append(entry.getKey()).append(",");
sqlBuffer.append("</if>");
}
sqlBuffer.append("</trim>");
sqlBuffer.append("<trim prefix=\"VALUES (\" suffix=\")\" suffixOverrides=\",\">");
for (String field : columnMap.values()) {
sqlBuffer.append("<if test=\"").append(field).append(" != null\">");
sqlBuffer.append("#{").append(field).append("} ,");
sqlBuffer.append("</if>");
}
sqlBuffer.append("</trim>");
sqlBuffer.append("</script>");
return sqlBuffer.toString();
}

public String buildUpdateSelective(ProviderContext context) {
final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName();
Map<String, String> columnMap = getColumnMap(context);
StringBuilder sqlBuffer = new StringBuilder();
sqlBuffer.append("<script>");
sqlBuffer.append("update ");
sqlBuffer.append(tableName);
sqlBuffer.append("<set>");
for (Map.Entry<String, String> entry : columnMap.entrySet()) {
sqlBuffer.append("<if test=\"").append(entry.getValue()).append(" != null\">");
sqlBuffer.append(entry.getKey()).append(" = #{").append(entry.getValue()).append("} ,");
sqlBuffer.append("</if>");
}
sqlBuffer.append("</set>");
// For simplicity, there is no @Id annotation here, using default id directly
sqlBuffer.append("where id = #{id}");
sqlBuffer.append("</script>");
return sqlBuffer.toString();
}

public String buildGetByEntityQuery(ProviderContext context) {
final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName();
Map<String, String> columnMap = getColumnMap(context);
StringBuilder sqlBuffer = new StringBuilder();
sqlBuffer.append("<script>");
sqlBuffer.append("select * from ");
sqlBuffer.append(tableName);
sqlBuffer.append("<where>");
for (Map.Entry<String, String> entry : columnMap.entrySet()) {
sqlBuffer.append("<if test=\"").append(entry.getValue()).append(" != null\">");
sqlBuffer.append("and ").append(entry.getKey()).append(" = #{").append(entry.getValue()).append("}");
sqlBuffer.append("</if>");
}
sqlBuffer.append("</where>");
sqlBuffer.append("</script>");
return sqlBuffer.toString();
}

}
Expand Up @@ -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();
}
}

}
@@ -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.
Expand All @@ -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() {
Expand Down