Wie Verwenden von Anmerkungen mit iBatis (Mybatis) für eine IN-Abfrage?
-
26-09-2019 - |
Frage
Wir möchten nur Anmerkungen mit Mybatis verwenden; Wir versuchen wirklich xml zu vermeiden. Wir versuchen, eine „IN“ Klausel zu verwenden:
@Select("SELECT * FROM blog WHERE id IN (#{ids})")
List<Blog> selectBlogs(int[] ids);
Mybatis ist nicht in der Lage scheint das Array von ints herauszusuchen und diejenigen, in die resultierende Abfrage setzen. Es scheint „fail leise“ und wir keine Ergebnisse zurück.
Es sieht aus wie wir diese mit Hilfe von XML-Mappings erreichen können, aber wir sollten wirklich so zu vermeiden. Gibt es eine korrekte Annotation Syntax für das?
Lösung
Ich glaube, das ist eine Nuance von Prepared Statements der jdbc und nicht Mybatis. Es gibt einen Link hier , die dieses Problem und bietet verschiedene Lösungen erläutert. Leider ist keine dieser Lösungen für Ihre Anwendung lebensfähig, aber es ist immer noch ein gut, die Grenzen der vorbereiteten Anweisungen in Bezug auf eine „IN“ Klausel zu verstehen lesen. Eine Lösung (vielleicht suboptimal) auf der DB-spezifische Seite der Dinge zu finden. Zum Beispiel in postgresql, könnte man verwenden:
"SELECT * FROM blog WHERE id=ANY(#{blogIds}::int[])"
"ANY" ist die gleiche wie "IN" und ":: int []" ist der Typ das Argument in ein Array von ints Gießen. Das Argument, dass in der Erklärung zugeführt wird, sollte in etwa so aussehen:
"{1,2,3,4}"
Andere Tipps
Ich glaube, die Antwort ist das gleiche wie in diese Frage . Sie können, indem Sie die folgende Mybatis dynamische SQL in Ihren Anmerkungen verwenden:
@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);
Das <script>
Element ermöglicht die dynamische SQL-Parsing und Ausführung für die Anmerkung. Es muss sehr erster Inhalt des Query-String sein. Es darf nichts vor ihm sein, nicht einmal Leerraum.
Beachten Sie, dass die Variablen, die Sie in den verschiedenen XML-Script-Tags folgen den gleichen Namenskonventionen wie regelmäßige Abfragen verwenden können, wenn Sie also auf Ihre Methodenargumente beziehen möchten Namen anders als „param1“ mit „param2“ usw. .. Sie benötigen jedes Argument mit einer @param Anmerkung Präfix.
Wir hatten einige der Forschung zu diesem Thema.
- einer der offiziellen Lösung von Mybatis ist Ihre dynamische SQL in
@Select("<script>...</script>")
zu setzen. Allerdings ist XML in Java Annotation Schreiben ziemlich plump. denken über dieses@Select("<script>select name from sometable where id in <foreach collection=\"items\" item=\"item\" seperator=\",\" open=\"(\" close=\")\">${item}</script>")
-
@SelectProvider
funktioniert gut. Aber es ist ein wenig kompliziert zu lesen. - PreparedStatement erlauben nicht gesetzt Sie eine Liste von Integer.
pstm.setString(index, "1,2,3,4")
Ihre SQL wie dieseselect name from sometable where id in ('1,2,3,4')
lassen. Mysql konvertiert Zeichen'1,2,3,4'
Nummer1
. - FIND_IN_SET Sie funktioniert nicht mit mysql-Index.
Schauen Sie in zu Mybatis dynamischen SQL-Mechanismus wird durch SqlNode.apply(DynamicContext)
umgesetzt. Allerdings wird @Select ohne <script></script>
Anmerkung nicht Parameter übergeben über DynamicContext
siehe auch
-
org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
-
org.apache.ibatis.scripting.xmltags.DynamicSqlSource
-
org.apache.ibatis.scripting.xmltags.RawSqlSource
So
- Lösung 1: Verwenden Sie @SelectProvider
- Lösung 2: Erweitern LanguageDriver die wird immer Kompilierung SQL
DynamicSqlSource
. Aber man noch überall zu schreiben\"
hat. - Lösung 3: Erweitern LanguageDriver die eigene Grammatik zu Mybatis umwandeln kann.
- Lösung 4: Schreiben Sie Ihre eigene LanguageDriver die SQL mit einigen Template-Renderer zu kompilieren, wie Mybatis-Geschwindigkeit Projekt der Fall ist. Auf diese Weise können Sie auch groovy integrieren.
Ihr Projekt nehmen Lösung 3 und hier ist der Code:
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);
}
}
Und die Nutzung:
@Lang(MybatisExtendedLanguageDriver.class)
@Select("SELECT " + COLUMNS + " FROM sometable where id IN (#{ids})")
List<SomeItem> loadByIds(@Param("ids") List<Integer> ids);
Ich habe einen kleinen Trick in meinem Code.
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);
}
Und ich verwenden, um diesen MyHandler in 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;
Es funktioniert jetzt :) Ich hoffe, dass dies dazu beitragen wird jemand.
Evgeny
Andere Option sein kann
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);
}
}
In meinem Projekt sind wir bereits mit Google Guava, so dass eine schnelle Abkürzung ist.
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));
}
}
In Oracle, verwende ich eine Variante von Tom Kyte des tokenizer unbekannt Liste Größen zu handhaben (bei Oracle 1k Grenze auf einer IN-Klausel und die Verschlimmerung von mehrer in tut es zu umgehen). Dies ist für varchar2, aber es kann für Zahlen angepasst werden (oder Sie können vertrauen nur auf Oracle zu wissen, dass ‚1‘ = 1 / Schauder).
Angenommen, Sie übergeben oder führen Mybatis Beschwörungen ids
als String zu bekommen, es zu benutzen:
select @Select("SELECT * FROM blog WHERE id IN (select * from table(string_tokenizer(#{ids}))")
Der Code:
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;
Sie können einen benutzerdefinierten Typ-Handler verwenden, um dies zu tun. Zum Beispiel:
public class InClauseParams extends ArrayList<String> {
//...
// marker class for easier type handling, and avoid potential conflict with other list handlers
}
Registrieren Sie die folgenden Typ-Handler in Ihrer Mybatis config (oder in der Anmerkung angeben):
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
}
Sie können sie dann wie folgt verwenden
@Select("SELECT * FROM foo WHERE id IN (#{list})"
List<Bar> select(@Param("list") InClauseParams params)
Dies wird jedoch nicht Arbeit für MySQL, da der MySQL-Anschluss nicht unterstützt setArray()
für vorbereitete Anweisungen.
Eine mögliche Abhilfe für MySQL ist FIND_IN_SET
statt IN
zu verwenden:
@Select("SELECT * FROM foo WHERE FIND_IN_SET(id, #{list}) > 0")
List<Bar> select(@Param("list") InClauseParams params)
Und Ihr Typ-Handler wird:
@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 ) );
}
Hinweis: Ich weiß nicht, die Leistung von FIND_IN_SET
, dies so testen, ob es wichtig ist,