我可以将List作为参数传递给MyBatis映射器吗?

时间:2011-12-09 20:00:15

标签: java mybatis

我正在尝试在MyBatis中定义一个简单的@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内联
  ###设置参数时发生错误   ###原因:java.lang.NullPointerException       at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:8)       在org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:77)       at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:69)       at org.apache.ibatis.binding.MapperMethod.executeForList(MapperMethod.java:85)       at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:65)       at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:35)       在$ Proxy23.selectSpecificProductTypes(未知来源)       在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)       在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)       在org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)       在org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)       在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)       在org.apache.ibatis.executor.parameter.DefaultParameterHandler.setParameters(DefaultParameterHandler.java:73)       at org.apache.ibatis.executor.statement.PreparedStatementHandler.parameterize(PreparedStatementHandler.java:61)       在org.apache.ibatis.executor.statement.RoutingStatementHandler.parameterize(RoutingStatementHandler.java:43)       在org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:56)       在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)       在org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:72)       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)       在org.apache.ibatis.plugin.Invocation.proceed(Invocation.java:31)
          ......还有36个

我认为问题在于注释本身。这似乎是一个相当普遍的要求。我是否需要自己将List转换为String并将其作为String参数而不是List<Integer>传递?或者是否有其他语法将List作为参数传递给MyBatis注释?

6 个答案:

答案 0 :(得分:44)

我之前从未使用过注释和MyBatis;我总是走xml配置文件路径(并不意味着使用注释有任何问题;只是解释我无法帮助你)。

话虽如此,page 46 from the MyBatis user guide

  

<强>的foreach

     

动态SQL的另一个常见需求是需要迭代a   收集,通常建立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元素非常强大,允许你指定一个   集合,声明可以在里面使用的项目和索引变量   元素的主体。它还允许您指定打开和   关闭字符串,并在迭代之间添加一个分隔符。   元素很聪明,因为它不会意外地追加额外的   分隔符。

答案 1 :(得分:5)

在处理List之后,您可以使用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();
    }
    

答案 2 :(得分:4)

我最近面临着完全相同的问题。根据我的理解,您更喜欢使用Java mapper而不是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中使用它。

答案 3 :(得分:1)

如果您希望使用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复制)

答案 4 :(得分:0)

MyBatis支持直接列出参数。

假设这是您的图层:

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
    }

答案 5 :(得分:-1)

尽管这个问题是很多年前的,@ Dave的回答是正确的,但是今天我想知道如何通过java而不是xml config播种它,我得到了它并与大家分享。

    @Autowired
    PersonMapper personMapper;

    @Test
    public void testListParams(){
        String[] ids = {"1","2","3","4"};
        List<String> idList = Arrays.asList(ids);
        String idListToString = idList.toString().substring(1, idList.toString().length() - 1);
        List<Person> people = personMapper.selectByIds(idListToString);
        people.forEach(System.out::println);
    }

在PersonMapper.java

    @Select("select id,name,age from person where id in ( ${ids} )") // attention: here is $ not #, $ is use to replace with string, # is use to replace ? with param
    List<Person> selectByIds(@Param("ids") String ids);