Подтвердить что ты не робот

Могу ли я передать список в качестве параметра в MapBaris Mapper?

Я пытаюсь определить простую аннотацию @Select в MyBatis, чтобы получить коллекцию объектов на основе критериев, определенных в разделе 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-Инлайн
### Ошибка при настройке параметров ### Причина: java.lang.NullPointerException     at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:8)     at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:77)     at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:69)     на org.apache.ibatis.binding.MapperMethod.executeForList(MapperMethod.java:85)     at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:65)     на org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:35)     в $Proxy23.selectSpecificProductTypes(Неизвестный источник)     на com.mycompany.MySourceMapperDebug.testSelectSpecificEmployees(MySourceMapperDebug.java:60)     at sun.reflect.NativeMethodAccessorImpl.invoke0 (собственный метод)     at sun.reflect.NativeMethodAccessorImpl.invoke(Неизвестный источник)     at sun.reflect.DelegatingMethodAccessorImpl.invoke(Неизвестный источник)     в java.lang.reflect.Method.invoke(Неизвестный источник)     в junit.framework.TestCase.runTest(TestCase.java:154)     в junit.framework.TestCase.runBare(TestCase.java:127)     в junit.framework.TestResult $1.protect(TestResult.java:106)     в junit.framework.TestResult.runProtected(TestResult.java:124)     в junit.framework.TestResult.run(TestResult.java:109)     в junit.framework.TestCase.run(TestCase.java:118)     в junit.framework.TestSuite.runTest(TestSuite.java:208)     в junit.framework.TestSuite.run(TestSuite.java:203)     на org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.java:130)     at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)     на org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)     at 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.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) Вызывается: java.lang.NullPointerException     at org.apache.ibatis.type.UnknownTypeHandler.setNonNullParameter(UnknownTypeHandler.java:21)     at org.apache.ibatis.type.BaseTypeHandler.setParameter(BaseTypeHandler.java:23)     at org.apache.ibatis.executor.parameter.DefaultParameterHandler.setParameters(DefaultParameterHandler.java:73)     at org.apache.ibatis.executor.statement.PreparedStatementHandler.parameterize(PreparedStatementHandler.java:61)     at org.apache.ibatis.executor.statement.RoutingStatementHandler.parameterize(RoutingStatementHandler.java:43)     at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:56)     at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:40)     at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:216)     at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:95)     at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:72)     at sun.reflect.NativeMethodAccessorImpl.invoke0 (собственный метод)     at sun.reflect.NativeMethodAccessorImpl.invoke(Неизвестный источник)     at sun.reflect.DelegatingMethodAccessorImpl.invoke(Неизвестный источник)     в java.lang.reflect.Method.invoke(Неизвестный источник)     на org.apache.ibatis.plugin.Invocation.proceed(Invocation.java:31)
        ... 36 больше

Я думаю, проблема в самой аннотации. Похоже, это было бы довольно распространенным требованием. Нужно ли преобразовать List в String и передать это как параметр String вместо List<Integer>? Или есть какой-то другой синтаксис для передачи List в качестве параметра аннотации MyBatis?

4b9b3361

Ответ 1

Я никогда не использовал аннотации и MyBatis раньше; Я всегда отправлял маршрут файла конфигурации xml (не подразумевая, что с использованием аннотаций что-то не так, просто объясняя, что я не могу вам помочь).

Как говорится, стр. 46 из руководства пользователя MyBatis:

Еогеасп

Другой распространенной необходимостью для динамического SQL является необходимость перебора сбор, часто для создания условия 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 очень мощный и позволяет указать коллекции, объявлять переменные item и index, которые могут использоваться внутри тело элемента. Он также позволяет вам указывать открывающиеся и закрывая строки и добавляя разделитель для размещения между итерациями. Элемент умен в том, что он случайно не добавит дополнительные сепараторы.

Ответ 2

С небольшими издержками вы можете использовать JAVA для создания динамической строки после обработки списка.

  • Определите поставщика выбора, в котором вы можете создать свой динамический запрос:

    @SelectProvider(type = com.data.sqlprovider.EmployeeSQLBuilder.class, method =      
     "selectSpecificEmployees")
     List<Employee> selectSpecificEmployees(@Param("employeeIds") List<Integer> 
      employeeIds);
    
  • В 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();
    }
    

Ответ 3

В последнее время я сталкиваюсь с теми же проблемами. Насколько я понимаю, вы предпочитаете использовать Java mapper вместо XML, что здесь и здесь.

Вот что я делаю, чтобы справиться с этим, используя: SqlBuilder.

Класс 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";
        }
    }

}

Преобразователь:

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

Что это.

EmployeeSqlBuilder будет динамически генерировать оператор sql. Я использую функцию getSqlConditionCollection для выполнения логических манипуляций. Конечно, вы можете инкапсулировать getSqlConditionCollection как статическую функцию в классе, что я и делаю в реальном проекте, тогда вы можете легко использовать его из другого SqlBuilder.

Ответ 4

Если вы хотите использовать 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)

Ответ 5

MyBatis поддерживает список параметров напрямую.

Предположим, это слой Дао:

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

Вы хотите передать список идентификаторов. тогда в вашем 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
    }