実現したいこと
- 画面遷移時に登録画面に入力した値をDBに正常に登録して、顧客リストを表示したい
前提
初投稿です。
下記サイトを基に、Spring boot を用いて顧客情報システムを作成しています。
(プログラミング逆引き辞典 SpringBoot入門:
https://learning-collection.com/springboot%e5%85%a5%e9%96%80/)
vol.9まではなんとかできたのですが、vol.10で躓いています。
具体的には、
登録画面 → DBに入力情報送信 → 顧客リスト表示
という流れが上手く行きません。
ディレクトリ構造は以下の通りです。
発生している問題・エラーメッセージ
とても長いので、エラーメッセージの上部分と、Caused byの部分を切り取ってお載せします。
Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback. Thu Oct 26 02:42:03 JST 2023 There was an unexpected error (type=Internal Server Error, status=500). not-null property references a null or transient value : com.example.demo.domain.Customer.name org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value : com.example.demo.domain.Customer.name ~略~ Caused by: org.hibernate.PropertyValueException: not-null property references a null or transient value : ~略~
該当のソースコード
(見苦しい点あると思いますが、ご容赦ください)
↓CustomerController.java (コントローラー)
Java
1package com.example.demo.web; 2 3import java.util.List; 4 5import org.springframework.beans.BeanUtils; 6import org.springframework.beans.factory.annotation.Autowired; 7import org.springframework.stereotype.Controller; 8import org.springframework.ui.Model; 9import org.springframework.web.bind.annotation.GetMapping; 10import org.springframework.web.bind.annotation.ModelAttribute; 11import org.springframework.web.bind.annotation.PostMapping; 12 13import com.example.demo.domain.Customer; 14import com.example.demo.service.CustomerService; 15 16@Controller //コントローラークラスを示す 17public class CustomerController { 18 @Autowired 19 CustomerService customerService; 20 21 @GetMapping("/") //HTTPリクエストのGETメソッドが呼ばれた時にそのメソッドで処理をする 22 String list(Model model) { 23 List<Customer> customers = customerService.findAll();//customerService.findAll()メソッドでDBのレコードを取得 24 model.addAttribute("customers", customers); 25 return "list"; 26 } 27 28 @GetMapping("create")// http://localhost/create/へのGETリクエスト時の処理 29 String create(@ModelAttribute CustomerForm customerForm) { 30 //↑Modelオブジェクトにセット 31 //model.addAttribute("customerForm", customerForm)と同様の処理をする 32 return "create"; 33 } 34 35 @PostMapping("create") //URLクエリにcreateを設定したPOSTリクエスト. 顧客登録画面の登録ボタンを押した時に呼ばれるメソッド 36 String regist(@ModelAttribute("customerForm") CustomerForm customerForm) { 37 Customer customer = new Customer(); //Customerクラスからcustomerインスタンスを生成 38 BeanUtils.copyProperties(customerForm, customer); //customerFormの値をcustomerにコピー, つまり、新規登録画面で入力した値をCustomerクラスのインスタンスにコピーしている 39 customerService.insert(customer); //CustomerServiceクラスに登録したinsert()メソッドを使用 40 return "redirect:/"; //顧客登録画面にリダイレクト 41 } 42 43}
↓create.html (登録画面)
html
1<!DOCTYPE html> 2<html xmlns:th="http://www.thymeleaf.org"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>顧客登録</title> 6 </head> 7 <body> 8 <h1>顧客登録</h1> 9 <form th:action="@{/create}" th:object="${customerForm}" method="post"> 10 <table> 11 <tr> 12 <th>名前</th> 13 <td><input type="text" name="name" th:field="*{name}"></td> 14 </tr> 15 <tr> 16 <th>メールアドレス</th> 17 <td><input type="text" name="email" th:field="*{email}"></td> 18 </tr> 19 </table> 20 <input type="submit" value="登録"> 21 </form> 22 </body> 23</html>
↓list.html (顧客リスト表示画面)
html
1<!DOCTYPE html> 2<html xmlns:th="http://www.thymeleaf.org"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>顧客一覧</title> 6 </head> 7 <body> 8 <a th:href="@{/create}">顧客登録</a> 9 <hr> 10 <h1>顧客一覧</h1> 11 <table> 12 <tr> 13 <th>ID</th> 14 <th>名前</th> 15 <th>メールアドレス</th> 16 </tr> 17 18 <tr th:each="customer : ${customers}"> 19 <td th:text="${customer.id}"></td> 20 <td th:text="${customer.name}"></td> 21 <td th:text="${customer.email}"></td> 22 </tr> 23 </table> 24 </body> 25</html>
↓CustomerForm.java (入力内容を渡すクラス)
※@Dataアノテーションが上手く働いていないっぽいので、getter, setterを明示的に入れています。
Java
1package com.example.demo.web; 2 3import lombok.Data; 4 5@Data 6public class CustomerForm { 7 // 画面とコントローラークラスの間で値を渡す役割をする。 8 private Integer id; 9 private String name; 10 private String email; 11 12//以下、@Dataアノテーションが上手く働いていないっぽいので、getter, setterを明示的に入れている 13//以下を抜くと動かなくなる 14 public void setId(Integer id) { 15 this.id = id; 16 } 17 18 public Integer getId() { 19 return id; 20 } 21 22 public void setName(String name) { 23 this.name = name; 24 } 25 26 public String getName() { 27 return name; 28 } 29 30 public void setEmail(String email) { 31 this.email = email; 32 } 33 34 public String getEmail() { 35 return email; 36 } 37}
↓Customer.java
※教材ではprivateでid, name emailを定義してましたが、publicにしろとのエラーが出たので、publicに変えています。
Java
1package com.example.demo.domain; 2 3import jakarta.persistence.Column; 4import jakarta.persistence.Entity; 5import jakarta.persistence.GeneratedValue; 6import jakarta.persistence.GenerationType; 7import jakarta.persistence.Id; 8import jakarta.persistence.Table; 9 10import lombok.AllArgsConstructor; 11import lombok.Data; 12import lombok.NoArgsConstructor; 13 14@Entity //JPAのエンティティであることを示す 15@Table(name="customer") //エンティティに対応するテーブル名を指定 16@Data //getter, setterメソッド等を生成 17@AllArgsConstructor //すべての引数を持つコンストラクタを生成 18@NoArgsConstructor //引数を持たないコンストラクタを生成 19public class Customer { 20 @Id //エンティティの主キー 21 @GeneratedValue(strategy = GenerationType.IDENTITY) //主キーが自動採番されることを示す 22 public Integer id; 23 @Column(nullable = false) //NotNull制約を示す 24 public String name; 25 public String email; 26} 27
↓CustomerService.java
Java
1package com.example.demo.service; 2 3import java.util.List; 4 5import org.springframework.beans.factory.annotation.Autowired; 6import org.springframework.stereotype.Service; 7import org.springframework.transaction.annotation.Transactional; 8//import org.springframework.context.annotation.Configuration; 9 10import com.example.demo.domain.Customer; 11import com.example.demo.repository.CustomerRepository; 12 13@Service //サービスクラスであることを示し、クラスのBeanをDIコンテナに登録する 14@Transactional //DBのトランザクション制御 15public class CustomerService { 16 @Autowired// DIを行う為に@Autowiredを付与する 17 CustomerRepository customerRepository; 18 public List<Customer> findAll(){ 19 //JpaRepositoryインターフェースを継承している為、customerRepository.findAllメソッドで 20 //「SELECT * FROM CUSTOMER」相当の処理を行う。 21 return customerRepository.findAllOrderById(); 22 } 23 24 public void insert(Customer customer) { //Customerクラスのcustomerテーブルに対してインサート 25 customerRepository.save(customer); 26 } 27 28 public void update(Customer customer) { //更新処理 29 customerRepository.save(customer); 30 } 31 32 public void delete(Integer id) { //削除処理 33 customerRepository.deleteById(id); 34 } 35 36 public Customer getReferenceById(Integer id){ //1件取得(https://b1san-blog.com/post/spring/spring-jpa/で修正) 37 return customerRepository.findById(id).orElseThrow(); 38 } 39 40}
↓CustomerRepository.java
Java
1package com.example.demo.repository; 2 3import java.util.List; 4 5import org.springframework.data.jpa.repository.JpaRepository; 6import org.springframework.data.jpa.repository.Query; 7 8import com.example.demo.domain.Customer; 9 10public interface CustomerRepository extends JpaRepository<Customer, Integer> { 11// JpaRepositoryを継承 12 @Query("SELECT u FROM Customer u ORDER BY u.id") //テーブル名(u)が抜けていた 13 List<Customer> findAllOrderById(); 14}
試したこと
こちら (https://stackoverflow.com/questions/6389600/not-null-property-references-a-null-or-transient-value) によると、「まだDBに保存されていない外部オブジェクトを参照している」とあるので、DBへの登録時にnullの値を入れようとしてしまっているためにDBに登録できず、発生しているエラーだと考えました。
そこで、customerFormの中身がnullになっているかどうかを確かめましたが、System.out.println(オブジェクト名) で標準出力しても値を見ることができず、確かめることが出来ませんでした。
また、こちら (https://teratail.com/questions/81322) によると、th:fieldに値をもたせることで、POSTリクエストにデータを送ることが出来るとあったのですが、th:fieldに値を持たせても、DBに登録ができないようでした。
また、こちら (https://kikutaro777.hatenablog.com/entry/2014/04/15/230916) から、BeanUtils.copyPropertiesの参照順序が違うかと考え、順序を変えたりもしましたが、ダメでした。
補足情報(FW/ツールのバージョンなど)
Spring boot 3.1.5
Java 17

回答1件
あなたの回答
tips
プレビュー