MybatisでNull判定するときのNullは大文字ではなく小文字で!!

# 初めに
Mybatisを使用して、SQLのWEREE条件にNullチェックをした際に下記のようなエラーではまりました。

```
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter 'NULL' not found. Available parameters are [id, param1]
 at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:96)
 at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:441)
 at com.sun.proxy.$Proxy72.selectOne(Unknown Source)
 at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:160)
 at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:87)
 at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:145)
 at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:86)
 at com.sun.proxy.$Proxy86.selectNullError(Unknown Source)
 at jp.com.app.mapper.NullErrorMapperTest.Error_Test(NullErrorMapperTest.java:24)
 at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
 at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
Caused by: org.apache.ibatis.binding.BindingException: Parameter 'NULL' not found. Available parameters are [id, param1]
 at org.apache.ibatis.binding.MapperMethod$ParamMap.get(MapperMethod.java:212)
 at org.apache.ibatis.scripting.xmltags.DynamicContext$ContextAccessor.getProperty(DynamicContext.java:120)
 at org.apache.ibatis.ognl.OgnlRuntime.getProperty(OgnlRuntime.java:3356)
 at org.apache.ibatis.ognl.ASTProperty.getValueBody(ASTProperty.java:121)
 at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
 at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:258)
 at org.apache.ibatis.ognl.ASTNotEq.getValueBody(ASTNotEq.java:51)
 at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
 at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:258)
 at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:586)
 at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:550)
 at org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java:46)
 at org.apache.ibatis.scripting.xmltags.ExpressionEvaluator.evaluateBoolean(ExpressionEvaluator.java:32)
 at org.apache.ibatis.scripting.xmltags.IfSqlNode.apply(IfSqlNode.java:34)
 at org.apache.ibatis.scripting.xmltags.MixedSqlNode.lambda$apply$0(MixedSqlNode.java:32)
 at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
 at org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:32)
 at org.apache.ibatis.scripting.xmltags.TrimSqlNode.apply(TrimSqlNode.java:55)
 at org.apache.ibatis.scripting.xmltags.MixedSqlNode.lambda$apply$0(MixedSqlNode.java:32)
 at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
 at org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:32)
 at org.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql(DynamicSqlSource.java:39)
 at org.apache.ibatis.mapping.MappedStatement.getBoundSql(MappedStatement.java:305)
 at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:87)
 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:151)
 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:145)
 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:76)
 at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:427)
 ... 72 more
```

# 結論
__\<if test="">\</if>__ や __\<when test="">\</when>__ などの条件は __「大文字」__ ではなく __「小文字」__ でかきましょう! 

```:OKパターン（nullが小文字）
<if test="id != null ">
    AND id = #{id}
</if>
```

```:NGパターン（NULLが大文字）
<if test="id != NULL ">
    AND id = #{id}
</if>
```

# 実験（興味のある人だけ参考に・・・）
__users_infomaion__ テーブルからidが1のユーザを取得するSQLの実行を想定します。
テーブルは以下を想定します。
```:Create文
CREATE TABLE users_infomaion (
  id integer,
  name VARCHAR(255)
);
```
以下のデータを追加します。
```:Insert文
INSERT INTO users_infomaion VALUES(1,'Tarou');
INSERT INTO users_infomaion VALUES(2,'Jirou');
INSERT INTO users_infomaion VALUES(3,'Saburou');
```
SQL結果取得用のBean(Entity)は以下です。
```:SQL結果取得用のBean
package jp.com.app.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class SampleEntity1Response {
    private Integer id;
    private String name;
}
```
__Mapper.java__ は以下です。それぞれ正常終了用(__selectRunning__)と異常終了用(__selectNullError__)を作成しています。
```:Mapper.java
package jp.com.app.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import jp.com.app.entity.SampleEntity1Response;

@Mapper
public interface NullErrorMapper {
    SampleEntity1Response selectRunning(@Param("id")Integer id);

    SampleEntity1Response selectNullError(@Param("id")Integer id);
}
```
```:Mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="jp.com.app.mapper.NullErrorMapper">
    <!-- 正常終了版 -->
    <select id="selectRunning" resultType="jp.com.app.entity.SampleEntity1Response">
        SELECT
            id,
            name
        FROM
            users_infomaion
        <where> 
            <!-- ※ nullが小文字 -->
            <if test="id != null ">
                AND id = #{id}
            </if>
        </where>
    </select>
    <!-- 異常終了版 -->
    <select id="selectNullError" resultType="jp.com.app.entity.SampleEntity1Response">
        SELECT
            id,
            name
        FROM
            users_infomaion
        <where> 
            <!-- ※ NULLが大文字 -->
            <if test="id != NULL ">
                AND id = #{id}
            </if>
        </where>
    </select>
</mapper>
```

Junitで直接Mapperを呼び出して実行することとします。
```:Testクラス
package jp.com.app.mapper;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;

import jp.com.app.entity.SampleEntity1Response;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class NullErrorMapperTest {
    @Autowired
    NullErrorMapper mapper;
    @Test
    public void Runnning_Test()throws Exception{
        Integer id = 1;
        SampleEntity1Response res = mapper.selectRunning(id);
        System.out.println("**************************************************************");
        System.out.println(res);
        System.out.println("**************************************************************");
    }

    @Test
    public void Error_Test()throws Exception{
        Integer id = 1;
        SampleEntity1Response res = mapper.selectNullError(id);
        System.out.println("**************************************************************");
        System.out.println(res);
        System.out.println("**************************************************************");
    }
}

```

```実行結果（正常終了パターン）
以下のようにResponseの値がコンソールに表示されたら成功です。
...省略
2022-08-30 15:06:29.502  INFO 25171 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
**************************************************************
SampleEntity1Response(id=1, name=Tarou)
**************************************************************
2022-08-30 15:06:29.714  INFO 25171 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
```...省略
```
以下のようなエラーが出たら失敗です。
```:実行結果（異常終了パターン）
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter 'NULL' not found. Available parameters are [id, param1]
 at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:96)
 at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:441)
 at com.sun.proxy.$Proxy72.selectOne(Unknown Source)
 at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:160)
 at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:87)
 at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:145)
 at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:86)
 at com.sun.proxy.$Proxy86.selectNullError(Unknown Source)
 at jp.com.app.mapper.NullErrorMapperTest.Error_Test(NullErrorMapperTest.java:26)
 at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
 at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
Caused by: org.apache.ibatis.binding.BindingException: Parameter 'NULL' not found. Available parameters are [id, param1]
 at org.apache.ibatis.binding.MapperMethod$ParamMap.get(MapperMethod.java:212)
 at org.apache.ibatis.scripting.xmltags.DynamicContext$ContextAccessor.getProperty(DynamicContext.java:120)
 at org.apache.ibatis.ognl.OgnlRuntime.getProperty(OgnlRuntime.java:3356)
 at org.apache.ibatis.ognl.ASTProperty.getValueBody(ASTProperty.java:121)
 at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
 at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:258)
 at org.apache.ibatis.ognl.ASTNotEq.getValueBody(ASTNotEq.java:51)
 at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
 at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:258)
 at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:586)
 at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:550)
 at org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java:46)
 at org.apache.ibatis.scripting.xmltags.ExpressionEvaluator.evaluateBoolean(ExpressionEvaluator.java:32)
 at org.apache.ibatis.scripting.xmltags.IfSqlNode.apply(IfSqlNode.java:34)
 at org.apache.ibatis.scripting.xmltags.MixedSqlNode.lambda$apply$0(MixedSqlNode.java:32)
 at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
 at org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:32)
 at org.apache.ibatis.scripting.xmltags.TrimSqlNode.apply(TrimSqlNode.java:55)
 at org.apache.ibatis.scripting.xmltags.MixedSqlNode.lambda$apply$0(MixedSqlNode.java:32)
 at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
 at org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:32)
 at org.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql(DynamicSqlSource.java:39)
 at org.apache.ibatis.mapping.MappedStatement.getBoundSql(MappedStatement.java:305)
 at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:87)
 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:151)
 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:145)
 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:76)
 at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:427)
 ... 72 more
```

# 最後に
MybatisはResponseのBean(Entity)の変数名の型とカラムの名前が一致していないと値がとれなかったりなどがあるので名前には気をつけましょう。ちなみに、筆者は「__resultMap__」と「「__resultType__」を見間違えてはまってしまったこともあります。注意深く確認しましょう！