タイトル
 メニューにないコーナーはTopからいけます
TOPJavaspring → This Page

3.4.3.(Criteria API)(複合主キーテーブルのCRUD) Spring+MVC+DB+Test構築サンプル

前提

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

本ページは先に以下の4ページの内容を実施してからの内容となります。
1.準備
2.共通部分構築
3.4.1.Criteria API 単一テーブルのCRUD
3.4.2.Criteria API N:1テーブルのCRUD


目次

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

1.PrimaryKeyクラスの作成

今回は「複合主キーテーブルのCRUD」ということで、
準備編で用意したテーブル「attendance」テーブルを使うことにします。
「attendance」テーブルは staffid, yyyymmdd カラムの2つがキー(複合主キー)となります。
図:attendance

Criteria API(というよりも JPA)を使う場合かつ複合主キーのテーブルを扱う場合、
複合主キーに対応したPrimaryKeyクラスを作成します。
プロジェクト名を右クリックして「New」>「Class」を選択します。
図:STS

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

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

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Embeddable;

@Embeddable
public class AttendancePk implements Serializable {
	private static final long serialVersionUID = 1L;
	
	@Column
	private Long staffid;
	
	@Column
	private Integer yyyymmdd;

	public AttendancePk() {
	}

	public AttendancePk(Long staffid, Integer yyyymmdd) {
		this();
		this.staffid = staffid;
		this.yyyymmdd = yyyymmdd;
	}

	public Long getStaffid() {
		return staffid;
	}

	public void setStaffid(Long staffid) {
		this.staffid = staffid;
	}

	public Integer getYyyymmdd() {
		return yyyymmdd;
	}

	public void setYyyymmdd(Integer yyyymmdd) {
		this.yyyymmdd = yyyymmdd;
	}

	public int hashCode() {
		int hashCode = 0;
		if (staffid != null) {
			hashCode ^= staffid.hashCode();
		}
		if (yyyymmdd != null) {
			hashCode ^= yyyymmdd.hashCode();
		}
		return hashCode;
	}

	public boolean equals(Object obj) {
		if (!(obj instanceof AttendancePk)) {
			return false;
		}
		
		AttendancePk target = (AttendancePk) obj;
		return ((this.staffid == null) ? (target.staffid == null) : this.staffid.equals(target.staffid))
				&& ((this.yyyymmdd == null) ? (target.yyyymmdd == null) : this.yyyymmdd.equals(target.yyyymmdd));
	}
	
}
クラス名の上に @Embeddable アノテーションをつけています。
これで別のエンティティクラスに「組み込む」ことができます。

「組み込む」ためにはさらに
・Serializable インタフェースを実装する
・hashCode メソッドを実装する
・equals メソッドを実装する
・@Id アノテーションは使わずに @Column アノテーションを使う
 (@Id アノテーションはエンティティクラス側で使う)
というポイントがあります。

テーブルの複合主キーに合わせて staffid, yyyymmdd をメンバフィールドに持ちます。
前述のように、主キーであっても @Column アノテーションを使います。
(@Id アノテーションは使わない!)

hashCode メソッド、equals メソッドは本サンプルを確認して下さい。
「こういうもんだ」と思って、自分で新しいPrimaryKeyクラスを作る際は
参考にしながら作って下さい。


2.Entityクラスの作成

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

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

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

import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Table;

@Entity
@Table(name="attendance")
public class Attendance {
	@EmbeddedId
	private AttendancePk id;
	
	@Column(nullable=false)
	private int state;
	
	public Attendance() {
	}
	
	public Attendance(AttendancePk id, int state) {
		this();
		this.id = id;
		this.state = state;
	}

	public AttendancePk getId() {
		return id;
	}

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

	public int getState() {
		return state;
	}

	public void setState(int state) {
		this.state = state;
	}
	
	@Override
	public String toString() {
		return "[staffid=" + id.getStaffid() + ",yyyymmdd=" + id.getYyyymmdd()
		+ ",state=" + state + "]";
	}
	
}
前章と同じ箇所は説明を割愛します。

テーブルの複合主キーとして先ほど作成した AttendancePk クラスを
@EmbeddedId アノテーションをつけてメンバフィールドに持ちます。
これで複合主キーを組み込めました。

あとは残りのカラム state をメンバフィールドに持っているだけです。
他は今までのエンティティクラスと変わりありません。


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

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

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

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

import java.util.List;

import jp.mitchy.entity.Attendance;
import jp.mitchy.entity.AttendancePk;

public interface AttendanceDao {
	public List<Attendance> getAllEntity();
	public Attendance findById(AttendancePk id);
	public void addEntity(Attendance entity);
	public void updateEntity(Attendance entity);
	public void removeEntity(Attendance data);
	public void removeEntity(AttendancePk id);
}
今回も今までと同じでそれぞれ
・全レコード取得
・1レコード取得
・レコード追加
・レコード更新
・レコード削除(引数がエンティティ)
・レコード削除(引数がID)
です

ただし、findById と removeEntity の引数は
PrimaryKeyクラスになっています。


4.Daoクラスの作成

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

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

作成された「AttendanceDaoImpl.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 javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaDelete;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.CriteriaUpdate;
import javax.persistence.criteria.Root;

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

import jp.mitchy.dao.AttendanceDao;
import jp.mitchy.entity.Attendance;
import jp.mitchy.entity.AttendancePk;

@Repository
public class AttendanceDaoImpl implements AttendanceDao {

	@Autowired
	private LocalContainerEntityManagerFactoryBean factory;
	
	@PersistenceContext
	private EntityManager manager;
	
	public AttendanceDaoImpl() {
		init();
	}
	
	public void init(){
		// @Autowired がうまく機能しない場合は以下のコメントを外す
		// SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
	}
	
	public List<Attendance> getAllEntity() {
		System.out.println("getAllEntity()");
		CriteriaBuilder builder = manager.getCriteriaBuilder();
		CriteriaQuery<Attendance> query = builder.createQuery(Attendance.class);
		Root<Attendance> root = query.from(Attendance.class);
		query.select(root);
		return manager.createQuery(query).getResultList();
	}

	public Attendance findById(AttendancePk id) {
		System.out.println("findById(AttendancePk)");
		CriteriaBuilder builder = manager.getCriteriaBuilder();
		CriteriaQuery<Attendance> query = builder.createQuery(Attendance.class);
		Root<Attendance> root = query.from(Attendance.class);
		// 以下のように直接複合キーを指定してはダメ
		// query.select(root).where(builder.and(
		//		builder.equal(root.get("staffid").as(Long.class), id.getStaffid()),
		//		builder.equal(root.get("yyyymmdd").as(Integer.class), id.getYyyymmdd())));
		// 以下のようにPrimaryKeyクラス経由で辿れば大丈夫
		// query.select(root).where(builder.and(
		// 		builder.equal(root.get("id").get("staffid").as(Long.class), id.getStaffid()),
		// 		builder.equal(root.get("id").get("yyyymmdd").as(Integer.class), id.getYyyymmdd())));
		// 以下が一番シンプルで簡単
		query.select(root).where(builder.equal(root.get("id").as(AttendancePk.class), id));
		return manager.createQuery(query).getSingleResult();
	}
	
	public void addEntity(Attendance entity) {
		System.out.println("addEntity(entity)");
		EntityManager manager = factory.getNativeEntityManagerFactory().createEntityManager();
		EntityTransaction transaction = manager.getTransaction();
		transaction.begin();
		// Criteria API は INSERT サポート対象外のため EntityManager でレコード追加
		manager.persist(entity);
		manager.flush();
		transaction.commit();
	}

	public void updateEntity(Attendance entity) {
		System.out.println("updateEntity(entity)");
		EntityManager manager = factory.getNativeEntityManagerFactory().createEntityManager();
		EntityTransaction transaction = manager.getTransaction();
		transaction.begin();
		CriteriaBuilder builder = manager.getCriteriaBuilder();
		CriteriaUpdate<Attendance> update = builder.createCriteriaUpdate(Attendance.class);
		Root<Attendance> root = update.from(Attendance.class);
		// 以下のように直接複合キーを指定してはダメ
		// update.set(root.get("state"), entity.getState()).where(builder.and(
		//		builder.equal(root.get("staffid").as(Long.class), entity.getId().getStaffid()),
		//		builder.equal(root.get("yyyymmdd").as(Integer.class), entity.getId().getYyyymmdd())));
		// 以下のようにPrimaryKeyクラス経由で辿れば大丈夫
		// update.set(root.get("state"), entity.getState()).where(builder.and(
		// 		builder.equal(root.get("id").get("staffid").as(Long.class), entity.getId().getStaffid()),
		// 		builder.equal(root.get("id").get("yyyymmdd").as(Integer.class), entity.getId().getYyyymmdd())));
		// 以下が一番シンプルで簡単
		update.set(root.get("state"), entity.getState()).where(
				builder.equal(root.get("id").as(AttendancePk.class), entity.getId()));
		Query query = manager.createQuery(update);
		query.executeUpdate();
		transaction.commit();
	}

	public void removeEntity(Attendance entity) {
		System.out.println("removeEntity(entity)");
		EntityManager manager = factory.getNativeEntityManagerFactory().createEntityManager();
		EntityTransaction transaction = manager.getTransaction();
		transaction.begin();
		CriteriaBuilder builder = manager.getCriteriaBuilder();
		CriteriaDelete<Attendance> delete = builder.createCriteriaDelete(Attendance.class);
		Root<Attendance> root = delete.from(Attendance.class);
		// 以下のように直接複合キーを指定してはダメ
		// delete.where(builder.and(
		//		builder.equal(root.get("staffid").as(Long.class), entity.getId().getStaffid()),
		//		builder.equal(root.get("yyyymmdd").as(Integer.class), entity.getId().getYyyymmdd())));
		// 以下のようにPrimaryKeyクラス経由で辿れば大丈夫
		// delete.where(builder.and(builder.equal(
		// 		root.get("id").get("staffid").as(Long.class), entity.getId().getStaffid()),
		// 		builder.equal(root.get("id").get("yyyymmdd").as(Integer.class), entity.getId().getYyyymmdd())));
		// 以下が一番シンプルで簡単
		delete.where(builder.equal(root.get("id").as(AttendancePk.class), entity.getId()));
		Query query = manager.createQuery(delete);
		query.executeUpdate();
		transaction.commit();
	}

	public void removeEntity(AttendancePk id) {
		System.out.println("removeEntity(AttendancePk)");
		Attendance entity = this.findById(id);
		this.removeEntity(entity);
	}

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

PrimaryKeyクラスを検索条件に指定する一番シンプルで簡単な実装にしていますが、
参考のためにコメントアウトで別の方法も記載してあります。


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

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

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

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

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

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


6.Serviceクラスの作成

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

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

作成された「AttendanceServiceImpl.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.AttendanceDao;
import jp.mitchy.dto.TestResultDto;
import jp.mitchy.entity.Attendance;
import jp.mitchy.entity.AttendancePk;
import jp.mitchy.service.AttendanceService;

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

	public TestResultDto<Attendance> execute(Attendance entity) {
		TestResultDto<Attendance> result = new TestResultDto<Attendance>();
		
		// INSERT
		dao.addEntity(entity);
		
		// UPDATE
		entity.setState(2);
		dao.updateEntity(entity);
		
		// DELETE
		dao.removeEntity(entity.getId());
		
		// SELECT 単体
		AttendancePk id2 = new AttendancePk(102L, 20180801);
		Attendance entity2 = dao.findById(id2);
		result.setEntity(entity2);
		
		// SELECT 複数
		List<Attendance> list = dao.getAllEntity();
		result.setList(list);

		return result;
	}
	
}
基本的に前の章で作成した内容から特に新しいものは出てきていないので
説明は割愛します。


7.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.Attendance;
import jp.mitchy.entity.AttendancePk;
import jp.mitchy.entity.Department;
import jp.mitchy.entity.Staff;
import jp.mitchy.service.AttendanceService;
import jp.mitchy.service.DepartmentService;
import jp.mitchy.service.StaffService;

@RequestMapping("/test/*")
@Controller
public class TestController {
	
	@Autowired
	private DepartmentService deptService;
	
	@Autowired
	private StaffService staffService;
	
	@Autowired
	private AttendanceService AttendanceService;
	
	@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";
	}
	
	@RequestMapping(value = "/attend", method = RequestMethod.GET)
	public String attend(Model model) {
		// ------------------------------
		// 複合主キー テーブルのCURD確認
		// ------------------------------
		
		AttendancePk id = new AttendancePk(201L, 20180101);
		Attendance entity = new Attendance(id, 1);
		
		// サービスの実行
		TestResultDto<Attendance> dto = AttendanceService.execute(entity);
		
		// 結果をセット
		model.addAttribute("data", dto.getEntity());
		model.addAttribute("list", dto.getList());

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

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


8.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/Attendance" var="messageUrl2" />
		<a href="${messageUrl2}">Staff Test</a><br/>
		<c:url value="/test/attend" var="messageUrl3" />
		<a href="${messageUrl3}">Attendance Test</a><br/>
	</body>
</html>


9.確認実行

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

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

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

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

以上で Criteria API を使った複合主キーテーブルのCRUDの
サンプル構築は完了です。


ダウンロード

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


更新履歴

2018/10/06 新規作成

TOPJavaspring → This Page
Valid CSS!