LoginSignup
9
10

More than 5 years have passed since last update.

Spring Data JPA

Last updated at Posted at 2016-03-20

目的

Spring Data JPAにて開発中、ハマったポイントと原因と対処を書き溜めていく

環境

Spring-boot
java8
STS
Spring-data-jpa
hivernate

問題と対処

事象1.0

BeanUtil.copyPropertiesにて、フォームから取得した値をDBへ登録するEntityへ転写する際、値が引き継がれずnullになるプロパティがある

import org.springframework.beans.BeanUtils

BeanUtils.copyProperties(フォーム, エンティティ);//左から右に値をコピー

画面からPOSTされた値をFormクラスを経由しコントローラで取得し、
javax.persistance.Entityクラスに引き継ぎ、
DBへ保存する。

フォームには値が入っているが、
DB側からNULLを許容しないエラーが発生する
画面から値の流れを調査したら、
copyPropatyにて値が引き継がれていなかった

原因と対処 1.0

Formクラスで定義されていたデータ型が、Entityと一致していない。
EntityでDoubleで定義されていた値に、FormクラスでIntegerで定義されたフィールドを割り当てていた。
springframeworkのcopyPropatyの場合、例外とはならず、nullが渡される。

その他 1.0

ちなみにハマったわけではないが、
フォームでは文字列で入力されている値を、Entityへ移動する際に、データ型をDateへ変換する処理

Util.java
// 明細部
    // 文字列の日付をdbのためdate型へ変換してFormからEntityへ
    public static MitsumoriDtl setMeisaiDateFormToEntity(MeisaiForm form, MitsumoriDtl entity) {

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");

        try {
            entity.setSys_create_ymdhm(sdf.parse(form.getSys_create_ymdhm()));
            entity.setSys_delete_ymdhm(sdf.parse(form.getSys_delete_ymdhm()));
            entity.setSys_update_ymdhm(sdf.parse(form.getSys_update_ymdhm()));

        } catch (Exception e) {
            e.printStackTrace();
        }
        return entity;

    }

事象2.0

JPQLにて検索処理に失敗する。

@Query("SELECT t FROM t_mitsumori_dtl t WHERE :hdrKey = t.mitsumori_hdr_no")
    List<MitsumoriDtl> findByHdrKey(@Param("hdrKey") String hdrKey);

t_mitsumori_dtlというテーブルから、JPQLを利用し、検索した結果をリストで返す。
検索はhdrKeyという変数の値で、mitsumori_hdr_no を絞り込む

しかし、t_mitsumori_dtlがマッピングできないというHibernateのエラーとなる

原因と対処 2.0

@Queryの中に入るのは、Entityクラスで、テーブルではない

package com.example.repository;

import java.util.Date;
import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.example.domain.MitsumoriDtl;

@Repository
@Transactional
public interface MitsumoriDtlRepository extends JpaRepository<MitsumoriDtl, Integer> {

    // hdrに紐づく明細を取得する
    @Query("SELECT t FROM MitsumoriDtl t WHERE :hdrKey = t.mitsumori_hdr_no")
    List<MitsumoriDtl> findByHdrKey(@Param("hdrKey") String hdrKey);


}

@Queryの中に入るのは、Entityクラスで、テーブルではない
上記に修正し解消することができた。

事象3.0

コントローラから、一部値を入れたエンティティを画面へ送り、
画面から残りの値を編集しデータの作成を行いたい

@RequestMapping(value = "editMeisai", method = RequestMethod.GET)
    String edit(Model model,@RequestParam Integer id,MeisaiForm form){
        // ここで取得しなければならないのは、紐づけるhdrのID番号だけで、そこへ新規の明細データをaddしていく

        // フォーム用
        // からの明細フォームを送る。hdridをあらかじめ入れておく
        MeisaiForm newMeisai = new MeisaiForm();
        newMeisai.setMitsumori_hdr_no(String.valueOf(id));
        model.addAttribute("newmeisai", newMeisai);
        return "views/meisai";
    }

この受け渡らせた値を受け取るフォーム側

        <form th:action="@{/testdb/editMeisai}" th:object="${newmeisai}"
                modelAttribute="meisaiForm" method="post">
                <fieldset>
                    <legend>登録</legend>

しかし、以下のエラーが出る。

dtl ---- : MitsumoriDtl(t_mitsumori_dtl_seq=null, mitsumori_hdr_no=1, mitsumori_dtl_no=123, mitsumori_dtl_mei=123, mitsumori_dtl_suryo=123.0, mitsumori_dtl_suryo_tani=123, mitsumori_dtl_tanka=123, mitsumori_dtl_gaku=123, mitsumori_dtl_biko=123, t_mitsumori_hdr_seq=1, sys_create_user=123, sys_create_ymdhm=Thu Jan 01 00:00:00 JST 2015, sys_create_class=123, sys_update_user=123, sys_update_ymdhm=Thu Jan 01 00:00:00 JST 2015, sys_update_class=123, sys_delete_user=123, sys_delete_ymdhm=Thu Jan 01 00:00:00 JST 2015, sys_delete_class=123, sys_delete_no=123, sys_version=1, headerEntity=null)
2016-03-22 01:20:43.768 ERROR 5996 --- [nio-8080-exec-6] org.thymeleaf.TemplateEngine             : [THYMELEAF][http-nio-8080-exec-6] Exception processing template "views/meisai": Exception evaluating SpringEL expression: "#fields.hasErrors('mitsumori_hdr_no')" (views/meisai:29)
2016-03-22 01:20:43.800 ERROR 5996 --- [nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#fields.hasErrors('mitsumori_hdr_no')" (views/meisai:29)] with root cause

java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'newmeisai' available as request attribute

画面からmodelのnewmeisaiに紐づく値で
Neither BindingResult nor plain target object for bean name 'newmeisai' available as request attribute
リクエストのnewmeisaiは、BuildingResultでもplainでもないというエラーが発生している

原因と対処 3.0

画面からの入力を行うフォーム項目は、完全な状態かからの状態でなければならない。
中途半端に入力してしまうと、バリデーションが走ってしまい、適切に表示できない
つまり、${newmeisai}、は、直接ここから受け取ることはできず、プレーンなフォームオブジェクトを与えなければならない

 <form th:action="@{/testdb/editMeisai}" th:object="${newmeisai}"
                modelAttribute="meisaiForm" method="post">

ではどうやって
コントローラから受け取った値を、Formにバインドされた新規のフォームクラスへ受け渡すか

方針として
1. Formの外にコントローラからkey(${newmeisai}に入っているキーとなる値を持っているフィールド)を受け取るだけの要素を作る
2. javascriptで、それぞれのdom要素を取得し、${newmeisai}コントローラから渡ってきた値を渡したい*{mitsumori_hdr_no}に引き渡す
3. この操作はHTML読み込み完了後に呼び出されるfooterでやる、また、値受け取り用のフォームはhiddenタイプにして隠蔽する

以下のように、値受け取り専用のformを作成し、値を取得する

<!-- ここで値を受け取る -->
<form th:object="${newmeisai}" ><input type="text" id="pased_id"
                                name="mitsumori_hdr_no"
                                th:field="*{mitsumori_hdr_no}" class="form-control"
                                th:value="*{mitsumori_hdr_no}" /></form>
<!----------------------->
        <h1>明細</h1>
        <!-- 登録を行う  -->
        <div class="col-sm-12">
            <!-- th:objectでは、フォームクラス名の頭小文字を指定すると、フォームオブジェクトとHTMLフィールドをバインドでき、
    このクラスのフィールド名で *{フィールド名}で値を呼び出せる -->
            <form th:action="@{/testdb/editMeisai}" th:object="${meisaiForm}"
                modelAttribute="meisaiForm" method="post">
                <fieldset>
                    <legend>登録</legend>
                    <!-- 見積もり番号 -->
                    <div class="form-group"
                        th:classappend="${#fields.hasErrors('mitsumori_hdr_no')}? 'has-errorhas-feedback'">
                        <label for="mitsumori_hdr_no"
                            class="col-sm-3control-label">見積もり番号 </label>
                        <div class="col-sm-11">
                            <input type="text" id="mitsumori_hdr_no"
                                name="mitsumori_hdr_no"
                                th:field="*{mitsumori_hdr_no}" class="form-control"
                                th:value="${mitsumori_hdr_no}" /> 

上記は、HTMLでは以下のようにレンダリングされる


    <form><input type="text" id="pased_id" name="mitsumori_hdr_no" class="form-control" value="1" /></form>
        <h1>明細</h1>
        <!-- 登録を行う  -->
        <div class="col-sm-12">
            <!-- th:objectでは、フォームクラス名の頭小文字を指定すると、フォームオブジェクトとHTMLフィールドをバインドでき、
    このクラスのフィールド名で *{フィールド名}で値を呼び出せる -->
            <form modelAttribute="meisaiForm" method="post" action="/testdb/editMeisai">
                <fieldset>
                    <legend>登録</legend>
                    <!-- 見積もり番号 -->
                    <div class="form-group">
                        <label for="mitsumori_hdr_no" class="col-sm-3control-label">見積もり番号 </label>
                        <div class="col-sm-11">
                            <input type="text" id="mitsumori_hdr_no" name="mitsumori_hdr_no" class="form-control" value="" /> 
                        </div>

ボディ要素のしたに以下のjavascriptを追加し、
上のdom要素を取得し、コントローラから値を受けとているpassed_idの要素のvalueを
今回値を登録したいmitsumori_hdr_noのvalueへ移し替える

<footer>
<script type="text/javascript">
var passed_key = document.getElementById('pased_id');
var post_key = document.getElementById('mitsumori_hdr_no');
post_key.value = passed_key.value
</script>
</footer>

値が確かに引き渡されている。受け取り側のtypeをhidenにすれば自然に渡せる
スクリーンショット 2016-03-22 2.18.08.png

事象4.0

関連テーブルのデータを削除できない

  • 1対多の関連を持つテーブルがあるこれに対応した以下のドメインクラスがある
    hdrEntity (単数)
    meisaiDtl (多:関連テーブル)

このクラスhttp://qiita.com/yukihigasi/items/54e224ce346dbd89e981
- 関連テーブル側をrepositoryのdeleteメソッドで直接削除しようとした場合、削除されない

原因と対処 4.0

  • 原因
    元テーブル側を経由して関連テーブルの削除を行わなければならない
    関連テーブルだけを単純に
 mitsumoriDtlService.delete(id);

関連テーブル用のリポジトリ.delete(関連テーブルの主キー)   
で削除できない
元ドメインクラスが生きている限り、マネージャがまだメモリ上に保持している

  • 対処
    元テーブル側から削除する
    元テーブルにコレクションクラスで保持されている関連テーブルの中から、
    今回削除したい関連テーブルオブジェクトを除去し、
    元テーブルのドメインクラスをアップデートする

以下で例示する

1. dtl(多)を検索してhdr(単一)の主キーを検索し、
2. hdrが保持するすべてのdtlを洗い、今回のdtlのidを見つけ、リストから削除する
3. 編集されたdtlのリストを持つhdrをアップデートする

コントローラクラス
@RequestMapping(value = "deleteMeisai", method = RequestMethod.POST)
    String deleteMeisai(@RequestParam Integer id) {
        // TODO 元エンティティから削除をしないと、マネージャ持されてしまうため、関連エンティティは直接けせない
        // dtlを検索してhdrの主キーを検索し、
        MitsumoriDtl dtl = mitsumoriDtlService.findOne(id);
        // hdrのdtlを繰り返し今回のdtlのidを見つけ、削除する
        HeaderEntity hdr = mitsumoriHdrService.findOne(dtl.getT_mitsumori_hdr_seq());
        List<MitsumoriDtl> serchDeleteLst = hdr.getMitsumoriDtl();
        int i=0;
        int index=0;
        for (MitsumoriDtl adtl:serchDeleteLst){
            if(adtl.getT_mitsumori_dtl_seq() == id){
                index=i;
            }
            i++;
        }
        serchDeleteLst.remove(index);
        hdr.setMitsumoriDtl(serchDeleteLst);
        // hdrをアップデートする
        mitsumoriHdrService.update(hdr);

        // mitsumoriDtlService.delete(id);
        return "redirect:/testdb";
    }

以上で、関連テーブルのキーを元に、関連テーブルのレコードを削除することができる。


以後、何かあり次第追記いたします。

9
10
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
9
10