1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【個人開発】プロフィールサイト③Spring Boot × MyBatis でログインが通らない(password_hash が null)【ポートフォリオ】

1
Posted at

今日のエラー

Spring Boot + MyBatis + PostgreSQL で「ログインフォームから送信 → DB 照合」を実装した直後、正しいメール・パスワードを入れてもログインが通らない現象が出ました。

何が起きたか

renderでのデプロイOK、ブラウザでのアクセスOK、見た目OK、バリデーションチェックOK、新規登録画面の実装は大丈夫そう。
今登録したアドレスとパスワードでログイン…あれ、パスワード間違えたのかな?
(数回やり直し、別のアカウントを新規登録してみたりした)やっぱりログインできない!!
と、他の部分は何も問題なさそうに動いているかと思いきや、何かが間違っているようでした。

ログインの判定はこんな感じです。

User user = userMapper.findByEmail(form.getEmail());

if (user == null || !passwordEncoder.matches(form.getPassword(), user.getPasswordHash())) {
    // 失敗扱い
}

ユーザー自体は取得できていそうなのに、毎回 else ではなく失敗側に落ちる。
=> 「user.getPasswordHash()null になっている」のではないか、と当たりを付けました。


原因の一言まとめ

DB の列名(snake_case)と Java のプロパティ名(camelCase)の対応付けが崩れていた。

  • DB 列: password_hash
  • Java プロパティ: passwordHash

INSERT 側は #{passwordHash} のように MyBatis が Java 側の名前で参照していたため問題なく動いていました。
ところが SELECTfindByEmail では、結果セットの 列名(password_hashJava 側プロパティ(passwordHash へ自動でマッピングする想定が、設定上効いていなかった。

結果、User 型は返ってくるのに passwordHash だけ null という、いちばん気付きにくい状態になっていました。


どうやって原因にたどり着いたか(手順)

「動かない!」となったときに、思い込みで触り回らずに済む順番です。

  1. 失敗している地点を特定する
    今回はログイン判定の if の中。明らかにここでコケている。
  2. その地点の入力値が想定どおりかを確認する
    • user は本当に取れているか?(null なら DB 検索が失敗)
    • user.getPasswordHash() は値が入っているか?(null なら照合は必ず false)
  3. 「処理ロジック」だけでなく「データの受け渡し」も疑う
    • 入力 → コントローラ → サービス → Mapper → SQL → DB → Mapper → Java オブジェクト
    • 今回の怪しい所は最後の戻り:SQL 結果 → Java オブジェクトへのマッピング
  4. 値の流れを「逆向き」に辿る
    • getPasswordHash()findByEmail の戻り値 ← SQL の結果列
    • DB 側で SELECT password_hash FROM users を直接打って、値自体は存在することを確認。
      => DB ではなくマッピング側が怪しい、と確定。
  5. MyBatis のマッピング規約を疑う
    • password_hash ↔ プロパティ passwordHash
    • スネーク↔キャメルの自動変換が効いていない可能性。

ここまで来ると、直す場所が一気に絞れます。


どう直したか

UserMapper.xmlfindByEmail で、列名にエイリアスを付けて Java プロパティ名と完全一致させました。

<select id="findByEmail" parameterType="string"
        resultType="com.example.demo.domain.User">
    SELECT
        id,
        name,
        email,
        password_hash    AS passwordHash,
        introduction,
        avatar_image_url AS avatarImageUrl,
        created_at       AS createdAt,
        updated_at       AS updatedAt
    FROM users
    WHERE email = #{email}
    LIMIT 1
</select>

ポイントは 1 行だけ:

  • password_hash AS passwordHash
    これで MyBatis が 「この列はこの Java プロパティ」 と迷わなくなり、getPasswordHash() が正しい値を返すようになった。

これでログインは通るようになりました。


なぜ MyBatis ではこれが起きやすいのか

MyBatis は「SQL 結果のカラム名」と「Java の setter 名」を見て値を埋めます。
列名とプロパティ名が一致しないとき、いくつかの解決策があります。

  1. SQL 側でエイリアスを付ける(今回の方法)
    SELECT password_hash AS passwordHash ...
    
    • 一番直感的で、後から読み返したときも分かりやすい。
  2. <resultMap> を定義する
    • 列とプロパティの対応を XML 側で表で書いておく方法。
    • 関連テーブルや複雑なマッピングがあるなら、こちらの方が拡張しやすい。
  3. mybatis.configuration.map-underscore-to-camel-case=true を設定する
    • application.properties などで一括設定。
    • snake_case の列を camelCase に自動変換」してくれる。
    • 設定したつもりが効いていない/プロジェクトに入っていない、というのが今回のような事故の典型パターン。

つまり、password_hashpasswordHashどこで責任もって変換するか を決めていないと、片側(INSERT は明示)だけ動いて、もう片側(SELECT)は静かに null を返す、という現象になります。


実務目線での今後の対策

  • SELECT には基本エイリアスを付ける癖をつける
    created_at AS createdAt のように 1 行追加するだけで、将来の自分や他人を救う。
  • 「自動変換に頼る方針」と「手動で書く方針」をプロジェクトで決めておく
    迷うとバグになる。map-underscore-to-camel-case を使うかどうかは、入った時点で確認。
  • null バグは「結果オブジェクト全体」でなく「フィールド単位」で疑う
    「ユーザーは取れているのにパスワードだけ null」のような部分欠損が起きる。
  • DB と Java の名前規約を最初に揃えておく
    どうしても揃わない箇所だけ、エイリアスや resultMap で受け止める設計にする。
  • デバッグのときは「SQL ログを表示する設定」を一時的に有効にする
    実際に発行されている SQL と、戻ってきている列名を見るのが一番早い。

学び(自分への指差し確認)

  • 動かないとき、まず 「処理」ではなく「データの受け渡し」 を疑う。
  • マッピング層のバグは エラーが派手に出ないので、null を見たら一旦止まる
  • INSERT で動いていることは、SELECT で動く保証にはならない。
  • 1 行のエイリアスで、再現性のある修正ができる。

このパターンは MyBatis を使っている限り何度も出会うので、見つけ方の手順を体に入れておくと、次の課題では一瞬で直せます。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?