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

Enhanced ProviderSqlSource to support dynamic sqlSource. #1111

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,10 @@
Class<?> type();

String method();

/**
* Cache the generated SqlSource, avoiding every rebuild.
* @since 3.4.6
*/
boolean cacheSqlSource() default false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,10 @@
Class<?> type();

String method();

/**
* Cache the generated SqlSource, avoiding every rebuild.
* @since 3.4.6
*/
boolean cacheSqlSource() default false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,10 @@
Class<?> type();

String method();

/**
* Cache the generated SqlSource, avoiding every rebuild.
* @since 3.4.6
*/
boolean cacheSqlSource() default false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,10 @@
Class<?> type();

String method();

/**
* Cache the generated SqlSource, avoiding every rebuild.
* @since 3.4.6
*/
boolean cacheSqlSource() default false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -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.
Expand All @@ -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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to force languageDriver to XMLLanguageDriver?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@h3adache Velocity and Freemarker's languageDriver do not support StaticSqlSource.

When call the configuration.getdefaultscriptinglanguageinstance( ) method, it is possible to return velocity and freemarker's the languageDriver.

The main purpose is to be compatible with the original method of ProviderSqlSource.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer that you leave those to the implementations and not take the current implementations. This is a hidden behavior that might surprise people who try to use this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@h3adache I submitted a new PR: #1120

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

Expand All @@ -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<String, Object>());
return languageDriver.createSqlSource(configuration, sql, parameterType);
} catch (BuilderException e) {
throw e;
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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", 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<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 "";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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 @@ -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<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);
StringBuffer sqlBuffer = new StringBuffer();
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);
StringBuffer sqlBuffer = new StringBuffer();
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);
StringBuffer sqlBuffer = new StringBuffer();
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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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