web-dev-qa-db-ja.com

リストをパラメーターとしてMyBatisマッパーに渡すことはできますか?

My節で単純な@Selectアノテーションを定義して、IN句で定義された基準に基づいてオブジェクトのコレクションを取得しようとしています。 SQLは次のようになります。

SELECT * FROM employees WHERE employeeID IN (1, 2, 3);

リストは動的に生成されるため、リストに含まれるパラメーターの数はわかりません。次のような値のListを渡すだけです。

@Select("SELECT * FROM employees WHERE employeeID IN( #{employeeIds} )")
List<Employee> selectSpecificEmployees(@Param("employeeIds") List<Integer> employeeIds);

上記の注釈が定義されているMapperのインスタンスを作成し、次のように呼び出しています。

List<Integer> empIds = Arrays.asList(1, 2, 3);
List<Employee> result = mapper.selectSpecificEmployees(empIds);

これが機能しないことを発見しました。

org.Apache.ibatis.exceptions.PersistenceException:
###データベースのクエリエラー。原因:Java.lang.NullPointerException
###エラーには、
com.mycompany.MySourceMapper.selectSpecificEmployees-Inline
###パラメータの設定中にエラーが発生しました###原因:org.Apache.ibatisでorg.Apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.Java:8)でJava.lang.NullPointerExceptionが発生しました。 org.Apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.Java:69)のorg.Apache.ibatis.binding.MapperMethod.executeForList(MapperMethod。のsession.defaults.DefaultSqlSession.selectList(DefaultSqlSession.Java:77) Java:85)at org.Apache.ibatis.binding.MapperMethod.execute(MapperMethod.Java:65)at org.Apache.ibatis.binding.MapperProxy.invoke(MapperProxy.Java:35)at $ Proxy23.selectSpecificProductTypes(Unknown Source )com.mycompany.MySourceMapperDebug.testSelectSpecificEmployees(MySourceMapperDebug.Java:60)at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at Sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)at Sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)at Java.lang.reflect.Method.invoke(Unknown Source)at junit.fram junit.framework.TestCase.runBare(TestCase.Java:127)のjunit.framework.TestResult $ 1.protect(TestResult.Java:106)のjunit.framework.TestResultでework.TestCase.runTest(TestCase.Java:154) runProtected(TestResult.Java:124)at junit.framework.TestResult.run(TestResult.Java:109)at junit.framework.TestCase.run(TestCase.Java:118)at junit.framework.TestSuite.runTest(TestSuite.Java :208)org.Eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.Java:130)at org.Eclipse.jdtでjunit.framework.TestSuite.run(TestSuite.Java:203)でinternal.junit.runner.TestExecution.run(TestExecution.Java:38)at org.Eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.Java:467)at org.Eclipse.jdt.internal.junit。 runner.RemoteTestRunner.runTests(RemoteTestRunner.Java:683)at org.Eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.Java:390)at org.Eclipse.jdt.internal.junit.runner.RemoteTestRunner。 main(RemoteTestRunner.Java:197)原因: org.Apache.ibatis.type.BaseTypeHandler.setParameter(BaseTypeHandler.Java:23)のorg.Apache.ibatis.type.UnknownTypeHandler.setNonNullParameter(UnknownTypeHandler.Java:21)のorg.Apache.ibatisでのJava.lang.NullPointerException org.Apache.ibatis.executor.statement.PreparedStatementHandler.parameterize(PreparedStatementHandler.Java:61)at org.Apache.ibatis.executor.statement.RoutingStatementHandler.parameterize(でexecutor.parameter.DefaultParameterHandler.setParameters(DefaultParameterHandler.Java:73) RoutingStatementHandler.Java:43)org.Apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.Java:56)at org.Apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.Java:40)at org.Apache.ibatis org.Apache.ibatis.executor.BaseExecutor.query(BaseExecutor.Java:95)at org.Apache.ibatis.executor.CachingExecutor.query(CachingExecutor.Java:72)の.executor.BaseExecutor.queryFromDatabase(BaseExecutor.Java:216) )Su。のSun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)でn.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)at Sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)at Java.lang.reflect.Method.invoke(Unknown Source)at org.Apache.ibatis.plugin.Invocation.proceed( Invocation.Java:31)
...さらに36

問題は注釈自体にあると思います。これはかなり一般的な要件のようです。 Listを自分でStringに変換し、List<Integer>ではなくStringパラメーターとして渡す必要がありますか?または、ListをパラメータとしてMyBatisアノテーションに渡すための構文は他にありますか?

30
Bill the Lizard

これまでにアノテーションとMyBatisを使用したことはありません。私は常にxml構成ファイルのルートを行ってきました(注釈の使用に問題があることを意味するわけではありません。

つまり、 MyBatisユーザーガイドの46ページ

foreach

動的SQLのもう1つの一般的な必要性は、多くの場合IN条件を構築するためにコレクションを反復処理する必要があることです。例えば:

<select id="selectPostIn" resultType="domain.blog.Post">
    SELECT *
    FROM POST P
    WHERE ID in
    <foreach item="item" index="index" collection="list"
        open="(" separator="," close=")">
          #{item}
    </foreach>
  </select>

Foreach要素は非常に強力であり、コレクションを指定し、要素の本体内で使用できるアイテム変数とインデックス変数を宣言できます。また、開始文字列と終了文字列を指定し、繰り返しの間に配置するセパレータを追加することもできます。この要素は、余分な区切り文字を誤って追加しないという点で優れています。

44
Dave

少しのオーバーヘッドで、Javaを使用して、リストの処理後に動的な文字列を作成できます。

  1. 動的クエリを作成できる選択プロバイダーを定義します。

    @SelectProvider(type = com.data.sqlprovider.EmployeeSQLBuilder.class, method =      
     "selectSpecificEmployees")
     List<Employee> selectSpecificEmployees(@Param("employeeIds") List<Integer> 
      employeeIds);
    
  2. Com.data.sqlprovider.EmployeeSQLBuilder.classで、StringBuilderを使用して、クエリを生成します

     public String selectSpecificEmployees(Map<String, Object> parameters) {
        List<Integer> employeeIds = (List<Integer>) parameters.get("employeeIds");
        StringBuilder builder = new StringBuilder("SELECT id, name FROM employees where id IN (");
        for (int i : employeeIds) {
            builder.append(i + ",");
        }
        builder.deleteCharAt(builder.length() - 1);
    
        builder.append(")");
        System.out.println(builder.toString());
        return builder.toString();
    }
    
5
hemantvsn

最近、まったく同じ問題に直面しています。私の理解では、Javaの代わりにXMLマッパーを使用することを好みます。これはここで同じです。

以下は、 SqlBuilder を使用して対処するために行っていることです。

SQLビルダークラス:

public class EmployeeSqlBuilder {

    public String getEmployees(final List employeeIds) {

        String strSQL = new SQL() {{
            SELECT("*");
            FROM("employees");
            if (employeeIds != null) {
                WHERE(getSqlConditionCollection("employeeID", employeeIds));
            }
        }}.toString();

        return strSQL;
    }

    private String getSqlConditionCollection(String field, List conditions) {
        String strConditions = "";
        if (conditions != null && conditions.size() > 0) {
            int count = conditions.size();
            for (int i = 0; i < count; i++) {
                String condition = conditions.get(i).toString();

                strConditions += condition;
                if (i < count - 1) {
                    strConditions += ",";
                }
            }
            return field + " in (" + strConditions + ")";
        } else {
            return "1=1";
        }
    }

}

マッパー:

@SelectProvider(type = EmployeeSqlBuilder.class, method = "getEmployees")
List<RecordSubjectEx> getEmployees(@Param("employeeIds") List employeeIds);

それでおしまい。

EmployeeSqlBuilderは、sqlステートメントを動的に生成します。私は論理操作を行うために関数getSqlConditionCollectionを使用しています。もちろん、getSqlConditionCollectionをクラスの静的関数としてカプセル化することができます。これは実際のプロジェクトで行っていることで、他のSqlBuilderから簡単に使用できます。

4
Vigor

foreachと注釈を使用する場合は、次の構文を使用できます。

@Select("<script>" +
         "SELECT * FROM employees WHERE employeeID IN " +
           "<foreach item='item' index='index' collection='employeeIds'" +
             " open='(' separator=',' close=')'>" +
             " #{item}" +
           "</foreach>" +
         "</script>") 
List<Employee> selectSpecificEmployees(@Param("employeeIds") List<Integer> employeeIds);

(そこからコピー answer

1
Arnaud

MyBatisはList paramsを直接サポートします。

これがdaoレイヤーであると仮定します。

public List<User> getUsersByIds(List<Integer> ids);

iDリストを渡します。その後、mapper.xmlでそれらを使用できます。

    <select id="getUsersByIds" resultType="entity.User">
      select * from user
      where id = #{list[0]} or id = #{list[1]}
    </select>

#{list[0]}または#{list[1]}を使用してリスト値を取得できます。

    @Test
    public void getUsersByIds(){
        List<Integer> ids = new ArrayList<Integer>();
        ids.add(1);
        ids.add(2);
        List<User> users = userDao.getUsersByIds(ids);
        // you will get correct result
    }
0
bitfishxyz