Как использовать аннотации с IBATIS (MyBatis) для в запросе?
-
26-09-2019 - |
Вопрос
Мы хотели бы использовать только аннотации с 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.
Имели некоторые исследования по этой теме.
- Одно из официальных решений от 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>")
@SelectProvider
работает отлично. Но это немного сложно читать.- Подготовительноестерение не позволяет устанавливать список целого числа.
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
. - 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
, Так что проверьте это, если это важно