web-dev-qa-db-fra.com

Puis-je transmettre une liste en tant que paramètre à un mappeur MyBatis?

J'essaie de définir un simple @Select annotation dans MyBatis pour obtenir une collection d'objets basée sur des critères définis par une clause IN. Le SQL ressemble à quelque chose comme:

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

La liste est générée dynamiquement, donc je ne sais pas combien de paramètres elle aura. Je voudrais juste passer un List de valeurs, quelque chose comme:

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

Je crée une instance de Mapper où l'annotation ci-dessus est définie et je l'appelle comme suit:

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

J'ai découvert que cela ne fonctionne pas.

org.Apache.ibatis.exceptions.PersistenceException:
### Erreur lors de l'interrogation de la base de données. Cause: Java.lang.NullPointerException
### L'erreur peut impliquer
com.mycompany.MySourceMapper.selectSpecificEmployees-Inline
### L'erreur s'est produite lors de la définition des paramètres ### Cause: Java.lang.NullPointerException à org.Apache.ibatis.exceptions.ExceptionFactory.wrapException (ExceptionFactory.Java:8) à org.Apache.ibatis. session.defaults.DefaultSqlSession.selectList (DefaultSqlSession.Java:77) à org.Apache.ibatis.session.defaults.DefaultSqlSession.selectList (DefaultSqlSession.Java:69) à org.Apache.ibatis.binding.MapperMethodetist.executeForm Java: 85) à org.Apache.ibatis.binding.MapperMethod.execute (MapperMethod.Java:65) à org.Apache.ibatis.binding.MapperProxy.invoke (MapperProxy.Java:35) à $ Proxy23.selectSpecificProductTypes (Source inconnue ) sur com.mycompany.MySourceMapperDebug.testSelectSpecificEmployees (MySourceMapperDebug.Java:60) sur Sun.reflect.NativeMethodAccessorImpl.invoke0 (méthode native) sur Sun.reflect.NativeMethodAccessorImpl.inveg.Aflor.Implec (source inconnue). Source) sur Java.lang.reflect.Method.invoke (Source inconnue) sur junit.fram ework.TestCase.runTest (TestCase.Java:154) sur junit.framework.TestCase.runBare (TestCase.Java:127) sur junit.framework.TestResult $ 1.protect (TestResult.Java:106) sur junit.framework.TestResult. runProtected (TestResult.Java:124) sur junit.framework.TestResult.run (TestResult.Java:109) sur junit.framework.TestCase.run (TestCase.Java:118) sur junit.framework.TestSuite.runTest (TestSuite.Java : 208) sur junit.framework.TestSuite.run (TestSuite.Java:203) sur org.Eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run (JUnit3TestReference.Java:130) sur org.Eclipse.jdt. internal.junit.runner.TestExecution.run (TestExecution.Java:38) sur org.Eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests (RemoteTestRunner.Java:467) sur org.Eclipse.jdt.internal.junit. runner.RemoteTestRunner.runTests (RemoteTestRunner.Java:683) à org.Eclipse.jdt.internal.junit.runner.RemoteTestRunner.run (RemoteTestRunner.Java:390) à org.Eclipse.jdt.internal.junit.runnerRote main (RemoteTestRunner.Java:197) Causée par: Java.lang.NullPointerException sur org.Apache.ibatis.type.UnknownTypeHandler.setNonNullParameter (UnknownTypeHandler.Java:21) sur org.Apache.ibatis.type.BaseTypeHandler.setParameter (BaseTypeHandler.Javais: 23) executor.parameter.DefaultParameterHandler.setParameters (DefaultParameterHandler.Java:73) at org.Apache.ibatis.executor.statement.PreparedStatementHandler.parameterize (PreparedStatementHandler.Java:61) at org.Apache.ibat.starting.paratStators RoutingStatementHandler.Java:43) chez org.Apache.ibatis.executor.SimpleExecutor.prepareStatement (SimpleExecutor.Java:56) chez org.Apache.ibatis.executor.SimpleExecutor.doQuery (SimpleExecutor.Java:40) chez org.Apache.ibatis .executor.BaseExecutor.queryFromDatabase (BaseExecutor.Java:216) sur org.Apache.ibatis.executor.BaseExecutor.query (BaseExecutor.Java:95) sur org.Apache.ibatis.executor.CachingExecutor.query (CachingExecutor.Java ) sur Sun.reflect.NativeMethodAccessorImpl.invoke0 (Native Method) sur Su n.reflect.NativeMethodAccessorImpl.invoke (Source inconnue) chez Sun.reflect.DelegatingMethodAccessorImpl.invoke (Source inconnue) chez Java.lang.reflect.Method.invoke (Source inconnue) chez org.Apache.ibatis.plugin.Invocation.proceed ( Invocation.Java:31)
... 36 de plus

Je pense que le problème est dans l'annotation elle-même. Cela semble être une exigence assez courante. Dois-je convertir moi-même le List en String et le passer en tant que paramètre String au lieu d'un List<Integer>? Ou existe-t-il une autre syntaxe pour passer un List comme paramètre à une annotation MyBatis?

30
Bill the Lizard

Je n'ai jamais utilisé d'annotations et de MyBatis auparavant; J'ai toujours suivi la route du fichier de configuration xml (n'impliquant pas qu'il y a quelque chose de mal à utiliser des annotations; expliquant simplement que je ne peux pas vous aider là-bas).

Cela étant dit, page 46 du guide de l'utilisateur MyBatis :

pour chaque

Une autre nécessité courante pour SQL dynamique est la nécessité d'itérer sur une collection, souvent pour créer une condition IN. Par exemple:

<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>

L'élément foreach est très puissant et vous permet de spécifier une collection, de déclarer des variables d'élément et d'index qui peuvent être utilisées dans le corps de l'élément. Il vous permet également de spécifier des chaînes d'ouverture et de fermeture et d'ajouter un séparateur à placer entre les itérations. L'élément est intelligent en ce qu'il n'ajoutera pas accidentellement de séparateurs supplémentaires.

44
Dave

Avec un peu de surcharge, vous pouvez utiliser Java pour créer une chaîne dynamique après avoir traité la liste.

  1. Définissez un fournisseur de sélection où vous pouvez créer votre requête dynamique:

    @SelectProvider(type = com.data.sqlprovider.EmployeeSQLBuilder.class, method =      
     "selectSpecificEmployees")
     List<Employee> selectSpecificEmployees(@Param("employeeIds") List<Integer> 
      employeeIds);
    
  2. Dans com.data.sqlprovider.EmployeeSQLBuilder.class, à l'aide de StringBuilder, générez la requête

     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

Je suis confronté aux mêmes problèmes récemment. Pour ma compréhension, vous préférez utiliser le mappeur Java au lieu de XML, qui est le même ici.

Voici ce que je fais pour y faire face en utilisant: SqlBuilder .

La classe SQL Builder:

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

}

Le mappeur:

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

C'est ça.

EmployeeSqlBuilder générera dynamiquement l'instruction sql. J'utilise une fonction getSqlConditionCollection pour faire la manipulation logique. Bien sûr, vous pouvez encapsuler le getSqlConditionCollection comme une fonction statique dans une classe, ce que je fais dans un projet réel, puis vous pouvez l'utiliser facilement à partir d'autres SqlBuilder.

4
Vigor

Si vous souhaitez utiliser foreach et des annotations, vous pouvez utiliser cette syntaxe:

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

(copié de cela réponse )

1
Arnaud

MyBatis prend en charge la liste des paramètres directement.

Supposons que c'est votre couche Dao:

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

vous voulez passer une liste d'identifiants. puis, dans votre mapper.xml, vous pouvez simplement les utiliser:

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

vous pouvez utiliser #{list[0]} ou #{list[1]} pour obtenir la valeur de la liste.

    @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