Могу ли я передать список в качестве параметра в 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?
Ответы
Ответ 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
}