タイトル
TOPJavaspring → This Page

3.2.2.(Entity Manager)(N:1テーブルのCRUD+自由なクエリ) Spring+MVC+DB+Test構築サンプル

前提

このページに記載している内容は 2018/09/30 に書かれたものです。
掲載している画面や方法が将来的に変更されている場合があります。
また、掲載しているインストール方法は Windows 8.1 の場合です。
開発環境は
・Windows 8.1
・JDK 8
・STS(Spring Tool Suite) 3.9.5
・PostgreSQL 9.5.14
とします。

本ページは先に以下の3ページの内容を実施してからの内容となります。
1.準備
2.共通部分構築
3.2.1.Entity Manager 単一テーブルのCRUD


目次

1.Entityクラスの作成
2.Daoインタフェースの作成
3.Daoクラスの作成
4.Serviceインタフェースの作成
5.Serviceクラスの作成
6.TestController.java編集
7.index.jsp編集
8.確認実行

1.Entityクラスの作成

今回は「N:1テーブルのCRUD」ということで、
準備編で用意したテーブル「staff」テーブルを使うことにします。
「staff」テーブルは「department」テーブルと外部結合でつながっています。
「staff」テーブルからみると「department」テーブルとの関係は「N:1」です。
(複数のスタッフが同じ1つの部門に属している状態)
図:department

1テーブルにつき対応した1Entityクラスを作成します。
プロジェクト名を右クリックして「New」>「Class」を選択します。
図:STS

「New Java Class」ダイアログが表示されます。
・「Package」は「jp.mitchy.entity」と入力
・「Name」は「Staff」と入力
・それ以外の項目はそのまま
「Finish」ボタンを押します。
図:New Java Class

作成された「Staff.java」が開くので、内容を以下のように書き換えて保存しましょう。
package jp.mitchy.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

@Entity
@Table(name="staff")
@NamedQueries({
		@NamedQuery(name="Staff.sales",query="from Staff where deptid = 2"),
		@NamedQuery(name="Staff.rangeage",query="from Staff where age >= :min and age <= :max")
})
public class Staff {
	@Id
	@Column
	private Long id;
	
	@Column(length=50,nullable=false)
	private String name;
	
	@Column(nullable=false)
	private int age;
	
	@ManyToOne
	@JoinColumn(name="deptid", referencedColumnName="id")
	private Department department;
	
	public Staff() {
		department = new Department();
	}

	public Staff(Long id, String name, int age, Long deptId) {
		this();
		this.id = id;
		this.name = name;
		this.age = age;
		this.department.setId(deptId);
	}

	public Staff(Long id, String name, int age, Department department) {
		this();
		this.id = id;
		this.name = name;
		this.age = age;
		this.department = department;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public Department getDepartment() {
		return department;
	}

	@Override
	public String toString() {
		return "[id=" + id + ",name=" + name + ",age=" + age
				+ ",dept:id=" + department.getId() + ",dept:name=" + department.getName() + "]";
	}
	
}
前章と同じ箇所は説明を割愛します。
テーブルのカラムに合わせて id, name, age をメンバフィールドに持ちます。
カラムにある deptid のかわりに department をメンバフィールドに持ちます。

Department クラスのフィールドに @ManyToOne アノテーションをつけています。
これで Staff:Department が N:1 ということを設定できます。
@JoinColumn(name="deptid", referencedColumnName="id") アノテーションは
Staff テーブルと Department テーブルとの結合条件を示します。
(列名が同じ場合は省略可能)
Staff テーブルの deptid と Department テーブルの id で結合します。
後述しますがこれで Staff と Department のレコードを一度に取得することもできます。

そのため、toString() メソッドを見ると分かると思いますが、
レコード検索後は department.getId(), department.getName() で
Department テーブルの情報も取ることができます。

そして今回はオマケですが、もっと自由なクエリを利用したい場合のために
@NamedQuery アノテーションをクラス名の上につけています。
@NamedQuery アノテーションを複数使いたい場合は今回のように
@NamedQueries アノテーションでくくります。

@NamedQuery アノテーションには名前とクエリを指定します。
クエリに指定するのは SQL ではなく JPQL なので注意。


2.Daoインタフェースの作成

同じく1テーブルにつき対応した1DAOインタフェースを作成します。
プロジェクト名を右クリックして「New」>「Interface」を選択します。
図:STS

「New Java Interface」ダイアログが表示されます。
・「Package」は「jp.mitchy.dao」と入力
・「Name」は「StaffDao」と入力
・それ以外の項目はそのまま
「Finish」ボタンを押します。
図:New Java Interface

作成された「StaffDao.java」が開くので、内容を以下のように書き換えて保存しましょう。
package jp.mitchy.dao;

import java.util.List;

import jp.mitchy.entity.Staff;

public interface StaffDao {
	public List<Staff> getAllEntity();
	public Staff findById(Long id);
	public void addEntity(Staff entity);
	public void updateEntity(Staff entity);
	public void removeEntity(Staff data);
	public void removeEntity(Long id);
}
メソッド名を見てなんとなく何をするメソッドか想像はつくと思いますが、
上からそれぞれ
・全レコード取得
・1レコード取得
・レコード追加
・レコード更新
・レコード削除(引数がエンティティ)
・レコード削除(引数がID)
です


3.Daoクラスの作成

先ほど作成したインタフェースを実装したクラスを作成します。
同じく1テーブルにつき対応した1DAOクラスを作成します。
プロジェクト名を右クリックして「New」>「Class」を選択します。
図:STS

「New Java Class」ダイアログが表示されます。
・「Package」は「jp.mitchy.dao.impl」と入力
・「Name」は「StaffDaoImpl」と入力
・それ以外の項目はそのまま
「Finish」ボタンを押します。
図:New Java Class

作成された「StaffDaoImpl.java」が開くので、内容を以下のように書き換えて保存しましょう。
package jp.mitchy.dao.impl;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.stereotype.Repository;
// import org.springframework.web.context.support.SpringBeanAutowiringSupport;

import jp.mitchy.dao.StaffDao;
import jp.mitchy.entity.Staff;

@Repository
public class StaffDaoImpl implements StaffDao {
	
	@SuppressWarnings("unused")
	@Autowired
	private ApplicationContext context;
	
	@Autowired
	private LocalContainerEntityManagerFactoryBean factory;
	
	@PersistenceContext
	private EntityManager manager;
	
	public StaffDaoImpl() {
		init();
	}
	
	public void init(){
		// @Autowired がうまく機能しない場合は以下のコメントを外す
		// SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
	}

	@SuppressWarnings("unchecked")
	public List<Staff> getAllEntity() {
		System.out.println("getAllEntity()");
		Query query = manager.createQuery("from Staff");
		return query.getResultList();
	}

	public Staff findById(Long id) {
		System.out.println("findById(id)");
		Query query = manager.createQuery("from Staff where id = :id").setParameter("id", id);
		return (Staff) query.getSingleResult();
	}
	
	public void addEntity(Staff entity) {
		System.out.println("addEntity(entity)");
		// Transactionは共通のmanagerでは使えないのでここでmanagerを取り直す
		EntityManager manager = factory.getNativeEntityManagerFactory().createEntityManager();
		EntityTransaction transaction = manager.getTransaction();
		transaction.begin();
		manager.persist(entity);
		manager.flush();
		transaction.commit();
	}

	public void updateEntity(Staff entity) {
		System.out.println("updateEntity(entity)");
		// Transactionは共通のmanagerでは使えないのでここでmanagerを取り直す
		EntityManager manager = factory.getNativeEntityManagerFactory().createEntityManager();
		EntityTransaction transaction = manager.getTransaction();
		transaction.begin();
		manager.merge(entity);
		manager.flush();
		transaction.commit();
	}

	public void removeEntity(Staff entity) {
		System.out.println("removeEntity(entity)");
		// Transactionは共通のmanagerでは使えないのでここでmanagerを取り直す
		EntityManager manager = factory.getNativeEntityManagerFactory().createEntityManager();
		EntityTransaction transaction = manager.getTransaction();
		transaction.begin();
		manager.remove(entity);
		manager.flush();
		transaction.commit();
	}

	public void removeEntity(Long id) {
		System.out.println("removeEntity(id)");
		// Transactionは共通のmanagerでは使えないのでここでmanagerを取り直す
		EntityManager manager = factory.getNativeEntityManagerFactory().createEntityManager();
		// 前もって find して managed 状態にしておかないと remove で削除できない
		Staff entity = manager.find(Staff.class, id);
		EntityTransaction transaction = manager.getTransaction();
		transaction.begin();
		manager.remove(entity);
		manager.flush();
		transaction.commit();
	}
	
	@SuppressWarnings("unchecked")
	public List<Staff> getSalesEntity() {
		System.out.println("getSalesEntity()");
		Query query = manager.createNamedQuery("Staff.sales");
		return query.getResultList();
	}

	@SuppressWarnings("unchecked")
	public List<Staff> getRangeAgesEntity(int min, int max) {
		System.out.println("getSalesEntity(min,max)");
		Query query = manager.createNamedQuery("Staff.rangeage").setParameter("min", min).setParameter("max", max);
		return query.getResultList();
	}

}
前の章で作成した「DepartmentDaoImpl」と同じような箇所は説明を割愛します。

追加で解説するポイントの1つ目は検索です。
前の章で作成した「DepartmentDaoImpl」と同様に
manager.createQuery() で Query オブジェクトを作成したあとに
query.getResultList() または query.getSingleResult() で結果を取っていますが、
Staff エンティティの設定のおかげで実は Department の情報も一緒に取れています。

ポイントの2つ目は名前クエリの利用です。
オマケなので StaffDao インタフェースに定義すらしていませんが、
getSalesEntity メソッドと getRangeAgesEntity メソッドで
Staff エンティティに定義した NamedQuery を利用しています。
見ての通り、
Query query = manager.createNamedQuery("クエリ名");
で利用可能です。


4.Serviceインタフェースの作成

次にサービス層のインタフェース・クラスを作ります。
今回もまずはインタフェースを作成します。
プロジェクト名を右クリックして「New」>「Interface」を選択します。
図:STS

「New Java Interface」ダイアログが表示されます。
・「Package」は「jp.mitchy.service」と入力
・「Name」は「StaffService」と入力
・それ以外の項目はそのまま
「Finish」ボタンを押します。
図:New Java Interface

作成された「StaffService.java」が開くので、内容を以下のように書き換えて保存しましょう。
package jp.mitchy.service;

import jp.mitchy.dto.TestResultDto;
import jp.mitchy.entity.Staff;

public interface StaffService {
	public TestResultDto<Staff> execute(Staff entity);
}
エンティティを引数とし、TestResultDto を返す execute メソッドのみのシンプルなインタフェースです


5.Serviceクラスの作成

先ほど作成したインタフェースを実装したクラスを作成します。
プロジェクト名を右クリックして「New」>「Class」を選択します。
図:STS

「New Java Class」ダイアログが表示されます。
・「Package」は「jp.mitchy.service.impl」と入力
・「Name」は「StaffServiceImpl」と入力
・それ以外の項目はそのまま
「Finish」ボタンを押します。
図:New Java Class

作成された「StaffServiceImpl.java」が開くので、内容を以下のように書き換えて保存しましょう。
package jp.mitchy.service.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import jp.mitchy.dao.StaffDao;
import jp.mitchy.dto.TestResultDto;
import jp.mitchy.entity.Staff;
import jp.mitchy.service.StaffService;

@Service
public class StaffServiceImpl implements StaffService {
	
	@Autowired
	private StaffDao dao;
	
	public StaffServiceImpl() {
		init();
	}
	
	public void init(){
		// @Autowired がうまく機能しない場合は以下のコメントを外す
		// SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
	}

	public TestResultDto<Staff> execute(Staff entity) {
		TestResultDto<Staff> result = new TestResultDto<Staff>();
		
		// INSERT
		dao.addEntity(entity);
		
		// UPDATE
		entity.setName("退職太郎3");
		dao.updateEntity(entity);
		
		// DELETE
		dao.removeEntity(entity.getId());
		
		// SELECT 単体
		Staff entity2 = dao.findById(102L);
		result.setEntity(entity2);
		
		// SELECT 複数
		List<Staff> list = dao.getAllEntity();
		result.setList(list);

		return result;
	}
	
}
こちらも基本的に前の章で作成した「DepartmentServiceImpl」とほぼ同じなので
説明は割愛します。


6.TestController.java編集

jp.mitchy.controller にある TestController.java を変更し、
今回作成したサービスを呼び出して実行するようにします。
内容を以下のように書き換えて保存しましょう。
赤文字が前回から追加になる箇所です。
package jp.mitchy.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import jp.mitchy.dto.TestResultDto;
import jp.mitchy.entity.Department;
import jp.mitchy.entity.Staff;
import jp.mitchy.service.DepartmentService;
import jp.mitchy.service.StaffService;

@RequestMapping("/test/*")
@Controller
public class TestController {
	
	@Autowired
	private DepartmentService deptService;
	
	@Autowired
	private StaffService staffService;
	
	@RequestMapping(value = "/dept", method = RequestMethod.GET)
	public String dept(Model model) {
		// ------------------------------
		// 単テーブルのCURD確認
		// ------------------------------
		
		Department entity = new Department(4L, "新事業部");
		
		// サービスの実行
		TestResultDto<Department> dto = deptService.execute(entity);
		
		// 結果をセット
		model.addAttribute("data", dto.getEntity());
		model.addAttribute("list", dto.getList());

		// view/test/result.jsp を表示
		return "test/result";
	}
	
	@RequestMapping(value = "/staff", method = RequestMethod.GET)
	public String staff(Model model) {
		// ------------------------------
		// N:1 テーブルのCURD確認
		// ------------------------------
		
		Staff entity = new Staff(103L, "人事太郎3", 50, 1L);
		
		// サービスの実行
		TestResultDto<Staff> dto = staffService.execute(entity);
		
		// 結果をセット
		model.addAttribute("data", dto.getEntity());
		model.addAttribute("list", dto.getList());

		// view/test/result.jsp を表示
		return "test/result";
	}
	
}

こちらも基本的に前の章で作成したメソッドとほぼ同じなので
説明は割愛します。


7.index.jsp編集

最後に、作成した処理を呼び出せるように index.jsp を編集します。
src/main/webapp/index.jsp を開き、内容を以下のように書き換えて保存しましょう。
赤文字が前回から追加になる箇所です。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE html>

<html>
	<head>
		<meta charset="utf-8">
		<title>Welcome</title>
	</head> 
	<body>
		<c:url value="/test/dept" var="messageUrl1" />
		<a href="${messageUrl1}">Department Test</a><br/>
		<c:url value="/test/staff" var="messageUrl2" />
		<a href="${messageUrl2}">Staff Test</a><br/>
	</body>
</html>


8.確認実行

念のためにいつもの「Maven Clean」「Maven Install」をやっておきましょう。
エラーが出たら「Project」>「Clean」をやってから
再度「Maven Install」です。

次にプロジェクト名を右クリックして「Run As」>「Run On Server」を選択します。
図:STS

少し時間がかかりますが「Console」に状況が表示されていきます。
しばらくすると内蔵ブラウザが立ち上がり、「Staff Test」が表示されます。
図:ブラウザ

「Staff Test」のリンクをクリックしてみましょう。
リンク先 /test/staff に連動する TestController クラスの staff メソッドが呼び出され、
/WEB-INF/view/test/result.jsp が表示されることが確認できます。
テーブル操作も正常に実行されていますね。
(Consoleの出力内容も確認してみましょう)
図:ブラウザ

以上で Entity Manager を使ったN:1テーブルのCRUD+自由なクエリの
サンプル構築は完了です。


ダウンロード

作成したプロジェクトのソースをダウンロードできます。
322WebDbSample2EntityManager.zip


更新履歴

2018/09/30 新規作成

TOPJavaspring → This Page