كيفية استخدام التعليقات التوضيحية مع Ibatis (MyBatis) للاستعلام في الاستعلام؟

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

سؤال

نود استخدام التعليقات التوضيحية فقط مع MyBatis ؛ نحن نحاول حقًا تجنب XML. نحن نحاول استخدام جملة "In":

@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 للشرح. يجب أن يكون أول محتوى لسلسلة الاستعلام. لا شيء يجب أن يكون أمامه ، ولا حتى مساحة بيضاء.

لاحظ أن المتغيرات التي يمكنك استخدامها في علامات Script المختلفة 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. لا يسمح لك ReparedStatement بتعيين قائمة عدد صحيح. 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.

انظر إلى آلية SQL الديناميكية MyBatis ، تم تنفيذها بواسطة 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: تمديد LanguageRiver الذي سيقوم دائمًا بتجميع SQL إلى DynamicSqlSource. ومع ذلك ، لا يزال يتعين عليك الكتابة \" في كل مكان.
  • الحل 3: تمديد LanguageRiver الذي يمكنه تحويل القواعد الخاصة بك إلى MyBatis One.
  • الحل 4: اكتب LaTeGriver الخاص بك والذي يقوم بتجميع SQL مع بعض عارض القالب ، تمامًا كما يفعل مشروع MyBatis-Levicity. وبهذه الطريقة ، يمكنك حتى دمج 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;

إنه يعمل الآن :) آمل أن يساعد هذا شخصًا ما.

evgeny

يمكن أن يكون الخيار الآخر

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

في أوراكل ، أستخدم متغيرًا Tom Kyte's Tokenizer للتعامل مع أحجام قائمة غير معروفة (بالنظر إلى حد Oracle 1K على جملة في جملة وتفاقم عمل INS متعددة للالتفاف حوله). هذا بالنسبة لـ Varchar2 ، ولكن يمكن تصميمه للأرقام (أو يمكنك الاعتماد على Oracle مع العلم أن "1 '= 1 /shudder).

على افتراض أنك تمرير أو تنفيذ تعويذة 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 الخاص بك (أو حدد في التعليق التوضيحي):

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