ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • MyBatis. BindingException: parameter '~~' not found
    개발 기록 2023. 5. 31. 14:12
    728x90
    org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter 'userId' not found. Available parameters are [arg1, arg0, param1, param2]

     

    MyBatis를 사용하면 Mapper 부분에서 종종 만나는 오류이다.

    코드를 수정한 것이 없는데 개발을 하다 보면 갑자기 나오기도 한다.

    하나하나 파악해보겠다.

     

    환경:

    java 1.8

    spring boot

     

    원인:

    (아래의 코드들은 자바 버전에 따라서 개선된 부분이 있지만 큰 틀은 비슷하다.)

     

    오류가 난 부분부터 코드를 따라가다 보면  MapperMethod 클래스의 이너 클래스로 MethodSignature가 생성되는 부분이 있다. MethodSignature 생성자 안에서 ParamNameResolver가 생성 되는 부분을 보면 현재 호출된 함수의 
    파라메터 이름을 객체로 관리하는 부분이 나온다.

    대략 이런 구조..
      new MapperMethod()
          new MethodSignature()
             new ParamNameResolver()

     

    여기 까지 들어가서 볼 것은 hasParamAnnotation과 useActualParamName이다.

    hasParamAnnotation은 @Param을 사용했다면 여기서 true로 설정이 된다.

    if (annotation instanceof Param) {
        this.hasParamAnnotation = true;
        name = ((Param)annotation).value();
        break;
    }

    useActualParamName은 설정값인데 

    프로퍼티의 mybatis.configuration.use-actual-param-name의 값이 들어온다.(default:true)

    @Param을 사용하지 않고 use-actual-param-name에 아무 설정을 하지 않았다면

    getActualParamName() 함수가 실행될 것이다.

    if (name == null) {
        if (this.useActualParamName) {
            name = this.getActualParamName(method, paramIndex);
        }
    
        if (name == null) {
            name = String.valueOf(map.size());
        }
    }

    이제 다시 getActualParamName() 함수 속으로 코드를 따라가면

    ParamNameUtil를 지나 Executable.privateGetParameters()까지 도달하게 된다.

    여기서 문제의 코드와 만난다.

    private native Parameter[] getParameters0();
    
    private Parameter[] privateGetParameters () {
    	...
    	// Otherwise, go to the JVM to get them
    	getParameters0()    
        ...
    }

    네이티브 코드를 호출하는 함수인데 이 값이 항상 null이 나온다.

    이것이 null이 나오면 뒤에 이어지는 과정에서 오류 메세지에서 보이는
    arg1, arg0, param1, param2 와같은 값들이 파라미터의 이름으로 설정된다.

    해결 방법.

    몇가지 해결방법이 있다.

    1. @Param을 사용한다. (파라메터가 1개일 경우는 @Param을 안해도 된다)

    @Mapper
    ex) User getUserInfo(@Param("email") String email, @Param("name") String name)

    2. Map 이나 Object를 사용한다.

    @Mapper
    ex) User getUserInfo(Map condition)
    ex) User getUserInfo(UserInfo userInfo)

    3. 빌드시 javac 옵션으로 -parameters를 넣어준다.

    https://docs.oracle.com/en/java/javase/18/docs/specs/man/javac.html

     

    The javac Command

    Checks the switch blocks for fall-through cases and provides a warning message for any that are found. Fall-through cases are cases in a switch block, other than the last case in the block, whose code does not include a break statement, allowing code execu

    docs.oracle.com

    -parameters
      Generates metadata for reflection on method parameters. Stores formal parameter names of constructors and methods in the generated class file so that the method java.lang.reflect.Executable.getParameters from the Reflection API can retrieve them.

     

    maven 프로젝트이면 아래와 같은 build 설정에 플러그인을 추가하면 된다.

    <build>
        </plugins>
        ...
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <compilerArgs>
                        <arg>-parameters</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
    	...
        </plugins>
    </build>
    @Mapper
    ex) User getUserInfo(String email, String name)

    ----------------

    java 17의 gradle 프로젝트에서는 -parameters을 따로 설정하지 않아도 파라메터 이름을 가져올 수 있었다!

    다른 경우는 테스트해보지 않았다.

    728x90
    반응형

    댓글

Designed by Tistory.