【问题标题】:Can I use MyBatis to generate Dynamic SQL without executing it?可以不用MyBatis生成Dynamic SQL而不执行吗?
【发布时间】:2012-11-02 12:21:09
【问题描述】:

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

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

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

【问题讨论】:

    标签: java dynamic-sql mybatis


    【解决方案1】:

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

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

    MySQL 表 user 中包含此数据:

    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'
    

    【讨论】:

    • 对我来说,它显示 ? 而不是实际值。例如where login = ?。有什么解决方法吗?谢谢(我没有使用映射器类)
    • 你需要这样做:List&lt;ParameterMapping&gt; boundParams = boundSql.getParameterMappings(); for (int i = 0; i &lt; boundParams.size(); i++) { preparedStatement.setObject(i + 1, valuesMap.get(boundParams.get(i).getProperty())); }
    【解决方案2】:

    大家都知道怎么用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;"
    

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

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

    【讨论】:

      【解决方案3】:

      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);
      

      【讨论】:

        【解决方案4】:

        只是为了补充 Bogdan 的正确答案:如果您的接口具有更复杂的签名,则需要将带有 getter 的 JavaBean 传递给 getBoundSql()

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

        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();
        [...]
        

        【讨论】:

          【解决方案5】:
          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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-11-10
            • 1970-01-01
            • 2012-11-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多