Как использовать аннотации с IBATIS (MyBatis) для в запросе?

StackOverflow https://stackoverflow.com/questions/3428742

Вопрос

Мы хотели бы использовать только аннотации с MyBatis; Мы действительно пытаемся избежать XML. Мы пытаемся использовать предложение «в»:

@Select("SELECT * FROM blog WHERE id IN (#{ids})") 
List<Blog> selectBlogs(int[] ids); 

MyBatis, похоже, не может выбрать массив INTS и поместить их в полученный запрос. Кажется, «провалиться тихо», и мы не получаем никаких результатов.

Похоже, что мы могли бы выполнить это, используя отображения XML, но нам действительно хотелось бы избежать этого. Есть ли правильный синтаксис аннотации для этого?

Это было полезно?

Решение

Я считаю, что это нюанс подготовленных заявлений JDBC и не MyBatis. Есть ссылка здесь Это объясняет эту проблему и предлагает различные решения. К сожалению, ни одно из этих решений не является жизнеспособным для вашего приложения, однако, все еще хорошо читается, чтобы понять ограничения подготовленных утверждений в отношении предложения «в». Решение (возможно, субоптимальное) можно найти на конкретной стороне вещей DB. Например, в PostgreSQL можно использовать:

"SELECT * FROM blog WHERE id=ANY(#{blogIds}::int[])"

«Любая» такая же, как «в» и «:: INT []», является тип, отличающий аргумент в массив INTS. Аргумент, который подается в заявление, должен выглядеть что-то вроде:

"{1,2,3,4}"

Другие советы

Я считаю, что ответ такой же, как приведен в этот вопрос. Отказ Вы можете использовать MyBatis Dynamic SQL в ваших аннотациях, выполнив следующие действия:

@Select({"<script>",
         "SELECT *", 
         "FROM blog",
         "WHERE id IN", 
           "<foreach item='item' index='index' collection='list'",
             "open='(' separator=',' close=')'>",
             "#{item}",
           "</foreach>",
         "</script>"}) 
List<Blog> selectBlogs(@Param("list") int[] ids);

То <script> Элемент обеспечивает динамическое распределение SQL и выполнение для аннотации. Это должно быть очень первое содержание строки запроса. Ничто не должно быть перед ним, а не даже белым пространством.

Обратите внимание, что переменные, которые вы можете использовать в различных тегах сценариев XML, следуют тому же соглашениям о именовании в качестве обычных запросов, поэтому, если вы хотите обратиться к аргументам метода, используя имена, отличные от «Param1», «Param2», и т. Д. Нужно префикс каждый аргумент с аннотацией @param.

Имели некоторые исследования по этой теме.

  1. Одно из официальных решений от MyBatis - поставить свой динамический SQL в @Select("<script>...</script>"). Отказ Тем не менее, написание XML в аннотации Java вполне неуместно. думать об этом @Select("<script>select name from sometable where id in <foreach collection=\"items\" item=\"item\" seperator=\",\" open=\"(\" close=\")\">${item}</script>")
  2. @SelectProvider работает отлично. Но это немного сложно читать.
  3. Подготовительноестерение не позволяет устанавливать список целого числа. pstm.setString(index, "1,2,3,4") позволит вашему SQL подобно этому select name from sometable where id in ('1,2,3,4'). Отказ Mysql конвертирует chars '1,2,3,4' на номер 1.
  4. Find_in_set Не работает с индексом MySQL.

Посмотрите на MyBatis Dynamic SQL-механизм, он был реализован SqlNode.apply(DynamicContext). Отказ Тем не менее, @Select без <script></script> Аннотация не пройдет параметр через DynamicContext

смотрите также

  • org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
  • org.apache.ibatis.scripting.xmltags.DynamicSqlSource
  • org.apache.ibatis.scripting.xmltags.RawSqlSource

Так,

  • Решение 1: используйте @selectProvider
  • Решение 2: продлить язык, который всегда будет компилировать SQL DynamicSqlSource. Отказ Тем не менее, вы все еще должны писать \" повсюду.
  • Решение 3: продлить языка, который может преобразовать свою собственную грамматику в MyBatis One.
  • Решение 4: Напишите свой собственный язык, который компилируемый SQL с некоторыми шаблонами Renderer, так же как проект MyBatis-Velocity. Таким образом, вы можете даже интегрировать Groovy.

Мой проект принимает решение 3 и вот код:

public class MybatisExtendedLanguageDriver extends XMLLanguageDriver 
                                           implements LanguageDriver {
    private final Pattern inPattern = Pattern.compile("\\(#\\{(\\w+)\\}\\)");
    public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
        Matcher matcher = inPattern.matcher(script);
        if (matcher.find()) {
            script = matcher.replaceAll("(<foreach collection=\"$1\" item=\"__item\" separator=\",\" >#{__item}</foreach>)");
        }
        script = "<script>" + script + "</script>";
        return super.createSqlSource(configuration, script, parameterType);
    }
}

И использование:

@Lang(MybatisExtendedLanguageDriver.class)
@Select("SELECT " + COLUMNS + " FROM sometable where id IN (#{ids})")
List<SomeItem> loadByIds(@Param("ids") List<Integer> ids);

Я сделал небольшой трюк в моем коде.

public class MyHandler implements TypeHandler {

public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
    Integer[] arrParam = (Integer[]) parameter;
    String inString = "";
    for(Integer element : arrParam){
      inString = "," + element;
    }
    inString = inString.substring(1);        
    ps.setString(i,inString);
}

И я использовал этот myhandler в sqlmapper:

    @Select("select id from tmo where id_parent in (#{ids, typeHandler=ru.transsys.test.MyHandler})")
public List<Double> getSubObjects(@Param("ids") Integer[] ids) throws SQLException;

Это работает сейчас :) Я надеюсь, что это поможет кому-то.

Евгений

Другой вариант может быть

    public class Test
    {
        @SuppressWarnings("unchecked")
        public static String getTestQuery(Map<String, Object> params)
        {

            List<String> idList = (List<String>) params.get("idList");

            StringBuilder sql = new StringBuilder();

            sql.append("SELECT * FROM blog WHERE id in (");
            for (String id : idList)
            {
                if (idList.indexOf(id) > 0)
                    sql.append(",");

                sql.append("'").append(id).append("'");
            }
            sql.append(")");

            return sql.toString();
        }

        public interface TestMapper
        {
            @SelectProvider(type = Test.class, method = "getTestQuery")
List<Blog> selectBlogs(@Param("idList") int[] ids);
        }
    }

В моем проекте мы уже используем Google Guava, поэтому быстрый ярлык.

public class ListTypeHandler implements TypeHandler {

    @Override
    public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, Joiner.on(",").join((Collection) parameter));
    }
}

В Oracle я использую вариант Токеризатор Тома Кита Для обработки размеров неизвестных списков (дан предел 1K Oracle на оговорке в пункте, а обострение делать множественные, чтобы обойти его). Это для varchar2, но он может быть адаптирован для чисел (или вы могли бы просто полагаться на Oracle, зная, что «1» = 1 / содрогание).

Предполагая, что вы проходите или выполняете заклинания MyBatis, чтобы получить ids Как строка, чтобы использовать его:

select @Select("SELECT * FROM blog WHERE id IN (select * from table(string_tokenizer(#{ids}))")

Код:

create or replace function string_tokenizer(p_string in varchar2, p_separator in varchar2 := ',') return sys.dbms_debug_vc2coll is
    return_value SYS.DBMS_DEBUG_VC2COLL;
    pattern varchar2(250);
begin
    pattern := '[^(''' || p_separator || ''')]+' ;

    select
        trim(regexp_substr(p_string, pattern, 1, level)) token
    bulk collect into
        return_value
    from
        dual
    where
        regexp_substr(p_string, pattern, 1, level) is not null
    connect by
        regexp_instr(p_string, pattern, 1, level) > 0;

    return return_value;
end string_tokenizer;

Вы можете использовать обработчик пользовательского типа для этого. Например:

public class InClauseParams extends ArrayList<String> {
   //...
   // marker class for easier type handling, and avoid potential conflict with other list handlers
}

Зарегистрируйте следующий обработчик типа в вашем MyBatis Config (или укажите в своей аннотации):

public class InClauseTypeHandler extends BaseTypeHandler<InClauseParams> {

    @Override
    public void setNonNullParameter(final PreparedStatement ps, final int i, final InClauseParams parameter, final JdbcType jdbcType) throws SQLException {

        // MySQL driver does not support this :/
        Array array = ps.getConnection().createArrayOf( "VARCHAR", parameter.toArray() );
        ps.setArray( i, array );
    }
    // other required methods omitted for brevity, just add a NOOP implementation
}

Вы можете использовать их такие

@Select("SELECT * FROM foo WHERE id IN (#{list})"
List<Bar> select(@Param("list") InClauseParams params)

Однако это будет нет Работа для MySQL, потому что разъем MySQL не поддерживает setArray() для подготовленных утверждений.

Возможный обходной путь для MySQL является использование FIND_IN_SET вместо IN:

@Select("SELECT * FROM foo WHERE FIND_IN_SET(id, #{list}) > 0")
List<Bar> select(@Param("list") InClauseParams params)

И обработчик вашего типа становится:

@Override
    public void setNonNullParameter(final PreparedStatement ps, final int i, final InClauseParams parameter, final JdbcType jdbcType) throws SQLException {

        // note: using Guava Joiner! 
        ps.setString( i, Joiner.on( ',' ).join( parameter ) );
    }

Примечание: я не знаю производительность FIND_IN_SET, Так что проверьте это, если это важно

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top