16

我有一些复杂的查询要使用一些可选的过滤器来构建,MyBatis 似乎是生成动态 SQL 的理想候选者。

但是,我仍然希望我的查询在与应用程序的其余部分(不使用 MyBatis)相同的框架中执行。

所以我希望做的是严格使用 MyBatis 来生成 SQL,但从那里使用我的应用程序的其余部分来实际执行它。这可能吗?如果是这样,怎么做?

4

5 回答 5

15

虽然 MyBatis 被设计为在构建后执行查询,但您可以利用它的配置和一点“内部知识”来获得您需要的内容。

MyBatis 是一个非常好的框架,不幸的是它在文档方面缺乏,所以源代码是你的朋友。如果您仔细研究,您应该会遇到这些类:org.apache.ibatis.mapping.MappedStatement它们org.apache.ibatis.mapping.BoundSql是构建动态 SQL 的关键参与者。这是一个基本的使用示例:

user包含此数据的MySQL 表:

name    login
-----   -----
Andy    a
Barry   b
Cris    c

User班级:

package pack.test;
public class User {
    private String name;
    private String login;
    // getters and setters ommited
}

UserService界面:

package pack.test;
public interface UserService {
    // using a different sort of parameter to show some dynamic SQL
    public User getUser(int loginNumber);
}

UserService.xml映射器文件:

<mapper namespace="pack.test.UserService">
    <select id="getUser" resultType="pack.test.User" parameterType="int">
       <!-- dynamic change of parameter from int index to login string -->
       select * from user where login = <choose>
                                           <when test="_parameter == 1">'a'</when>
                                           <when test="_parameter == 2">'b'</when>
                                           <otherwise>'c'</otherwise>
                                        </choose>   
    </select>
</mapper>

sqlmap-config.file

<configuration>
    <settings>
        <setting name="lazyLoadingEnabled" value="false" />
    </settings>
    <environments default="development"> 
        <environment id="development"> 
            <transactionManager type="JDBC"/> 
            <dataSource type="POOLED"> 
                <property name="driver" value="com.mysql.jdbc.Driver"/> 
                <property name="url" value="jdbc:mysql://localhost/test"/> 
                <property name="username" value="..."/> 
                <property name="password" value="..."/> 
            </dataSource> 
        </environment> 
      </environments>
    <mappers>
        <mapper resource="pack/test/UserService.xml"/>
    </mappers>
</configuration>

AppTester显示结果:

package pack.test;

import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class AppTester {
    private static String CONFIGURATION_FILE = "sqlmap-config.xml";

    public static void main(String[] args) throws Exception {
        Reader reader = null;
        SqlSession session = null;
        try {

            reader = Resources.getResourceAsReader(CONFIGURATION_FILE);
            session = new SqlSessionFactoryBuilder().build(reader).openSession();
            UserService userService = session.getMapper(UserService.class);

            // three users retreived from index
            for (int i = 1; i <= 3; i++) {
                User user = userService.getUser(i);
                System.out.println("Retreived user: " + user.getName() + " " + user.getLogin());

                // must mimic the internal statement key for the mapper and method you are calling
                MappedStatement ms = session.getConfiguration().getMappedStatement(UserService.class.getName() + ".getUser");
                BoundSql boundSql = ms.getBoundSql(i); // parameter for the SQL statement
                System.out.println("SQL used: " + boundSql.getSql());
                System.out.println();
            }

        } finally {
            if (reader != null) {
                reader.close();
            }
            if (session != null) {
                session.close();
            }
        }
    }
}

结果:

Retreived user: Andy a
SQL used: select * from user where login =  'a'

Retreived user: Barry b
SQL used: select * from user where login =  'b'

Retreived user: Cris c
SQL used: select * from user where login =  'c'
于 2012-11-04T18:00:41.313 回答
3

大家都知道如何使用BoundSql.getSql()从MyBatis获取参数化查询字符串,像这样:

// get parameterized query
MappedStatement ms = configuration.getMappedStatement("MyMappedStatementId");
BoundSql boundSql = ms.getBoundSql(parameters);
System.out.println("SQL" + boundSql.getSql());
// SELECT species FROM animal WHERE name IN (?, ?) or id = ?

但现在您需要等式的另一半,即与问号对应的值列表:

// get parameters
List<ParameterMapping> boundParams = boundSql.getParameterMappings();
String paramString = "";
for(ParameterMapping param : boundParams) {
    paramString += boundSql.getAdditionalParameter(param.getProperty()) + ";";
}
System.out.println("params:" + paramString);
// "Spot;Fluffy;42;"

现在,您可以将其序列化以发送到其他地方以运行,或者您可以将其打印到日志中,这样您就可以将它们拼接在一起并手动运行查询。

*代码未经测试,可能是次要类型问题或类似问题

于 2016-12-08T17:08:36.767 回答
1

mybatis版本是3.4.5

实用类

要将mapper转换为sql,需要mapper接口类、方法名、参数和sqlSession。

        package util;

        import java.lang.reflect.Method;
        import java.text.DateFormat;
        import java.time.LocalDateTime;
        import java.time.format.DateTimeFormatter;
        import java.util.Date;
        import java.util.List;
        import java.util.Locale;
        import java.util.regex.Matcher;
        import org.apache.ibatis.binding.MapperMethod.MethodSignature;
        import org.apache.ibatis.mapping.BoundSql;
        import org.apache.ibatis.mapping.MappedStatement;
        import org.apache.ibatis.mapping.ParameterMapping;
        import org.apache.ibatis.reflection.MetaObject;
        import org.apache.ibatis.session.Configuration;
        import org.apache.ibatis.session.SqlSession;
        import org.apache.ibatis.type.TypeHandlerRegistry;
        import org.springframework.util.CollectionUtils;

        /**
         * @author zwxbest - 19-4-25
         */
        public class SqlUtil {

            public static String showSql(SqlSession sqlSession, Class mapperInterface, String methodName,
                Object[] params) {
                Configuration configuration = sqlSession.getConfiguration();
                MappedStatement ms = configuration.getMappedStatement(
                    mapperInterface.getName() + "." + methodName);

                Method sqlMethod = null;

                //find method equals methodName
                for (Method method : mapperInterface.getDeclaredMethods()) {
                    if (method.getName().equals(methodName)) {
                        sqlMethod = method;
                        break;
                    }
                }
                if (sqlMethod == null) {
                    throw new RuntimeException("mapper method is not found");
                }

                MethodSignature method = new MethodSignature(configuration, mapperInterface, sqlMethod);
                Object paramObject = method.convertArgsToSqlCommandParam(params);
                BoundSql boundSql = ms.getBoundSql(paramObject);
                Object parameterObject = boundSql.getParameterObject();
                List<ParameterMapping> parameterMappings = boundSql
                    .getParameterMappings();
                String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
                if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {
                    TypeHandlerRegistry typeHandlerRegistry = configuration
                        .getTypeHandlerRegistry();
                    if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        sql = sql.replaceFirst("\\?",
                            Matcher.quoteReplacement(getParameterValue(parameterObject)));
                    } else {
                        MetaObject metaObject = configuration.newMetaObject(
                            parameterObject);
                        for (ParameterMapping parameterMapping : parameterMappings) {
                            String propertyName = parameterMapping.getProperty();
                            if (metaObject.hasGetter(propertyName)) {
                                Object obj = metaObject.getValue(propertyName);
                                sql = sql
                                    .replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
                            } else if (boundSql.hasAdditionalParameter(propertyName)) {
                                Object obj = boundSql.getAdditionalParameter(propertyName);
                                sql = sql
                                    .replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
                            } else {
                                sql = sql.replaceFirst("\\?", "missing");
                            }
                        }
                    }
                }
                return sql;
            }

            /**
             * if param's type is `String`,add single quotation<br>
             *
             * if param's type is `datetime`,convert to string and quote <br>
             */
            private static String getParameterValue(Object obj) {
                String value = null;
                if (obj instanceof String) {
                    value = "'" + obj.toString() + "'";
                } else if (obj instanceof Date) {
                    DateFormat formatter = DateFormat
                        .getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
                    value = "'" + formatter.format(new Date()) + "'";
                } else if (obj instanceof LocalDateTime) {
                    value = "\'" + ((LocalDateTime) obj)
                        .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "\'";
                } else {
                    if (obj != null) {
                        value = obj.toString();
                    } else {
                        value = "";
                    }

                }
                return value;
            }
}

调用示例

sqlSession 由 Spring 注入。

@Autowired
private SqlSession sqlSession;

    String sql = SqlUtil
        .showSql(sqlSession, PromotionCodeMapper.class, "selectByPromotionCodeForUpdate",
            new Object[]{"111"});
    log.warn(sql);
于 2019-04-25T06:32:24.063 回答
0

getBoundSql()只是添加到 Bogdan 的正确答案:如果您的接口具有更复杂的签名,则需要将 JavaBean 传递给带有 getter 的接口参数。

假设您要根据登录号和/或用户名查询用户。您的界面可能如下所示:

package pack.test;
public interface UserService {
    // using a different sort of parameter to show some dynamic SQL
    public User getUser(@Param("number") int loginNumber, @Param("name") String name);
}

我省略了 Mapper 代码,因为它与本次讨论无关,但您在 AppTester 中的代码应变为:

[...]
final String name = "Andy";
User user = userService.getUser(i, name);
System.out.println("Retreived user: " + user.getName() + " " + user.getLogin());

// must mimic the internal statement key for the mapper and method you are calling
MappedStatement ms  = session.getConfiguration().getMappedStatement(UserService.class.getName() + ".getUser");
BoundSql boundSql = ms.getBoundSql(new Object() {
   // provide getters matching the @Param's in the interface declaration
   public Object getNumber() {
     return i;
   }
   public Object getName() {
     return name;
   }

});
System.out.println("SQL used: " + boundSql.getSql());
System.out.println();
[...]
于 2014-01-21T14:50:16.213 回答
0
public static void main(String[] args) throws Exception {

    String script = "<script>select * from table where 1 = 1<if test='id != null'>and id = ${id} </if></script>";

    System.out.println(buildSql(script));

}

private static String buildSql(String script) {

    LanguageDriver languageDriver = new XMLLanguageDriver();

    Configuration configuration = new Configuration();

    SqlSource sqlSource = languageDriver.createSqlSource(configuration, script, Object.class);

    Map<String, String> parameters = new HashMap<>();
    parameters.put("id", "1");

    BoundSql boundSql = sqlSource.getBoundSql(parameters);

    return boundSql.getSql();

}

使用 ${id} 而不是 #{id}

结果是: select * from table where 1 = 1 and id = 1

于 2021-08-19T06:09:02.813 回答