ホーム画面からユーザー一覧画面に遷移し、ユーザーの詳細を表示するアプリケーションを作成して、Spring JDBCの使い方について学びます⭐️
今回はRowCallbackHandlerを使って、ユーザー一覧画面からCSV出力する機能を作ります^^
構成は前回/これまでの記事を参考にしてください
⭐️前回の記事
【Java・SpringBoot】Spring JDBC / ResultSetExtractor(SpringBootアプリケーション実践編17)
#RowCallbackHandler
- RowCallbackHandlerでコールバックして、ユーザー一覧画面からCSV出力する機能を作ります
###コールバックとは
- 時間のかかるような処理を別のコールバック関数に実行依頼する
- その処理が終わるまで他の処理を行うことができる
- コールバック関数の処理が終わったら結果を受け取る
###RowCallbackHandlerの使い方
- RowCallbackHandlerをimplements
- 以下の例では、
processRow()
メソッド内で、ResultSet
から取得した値をsample.csv
に書き込む処理をしている -
RowCallbackHandlerでは、既に1回
next()
メソッドが実行された状態になっている- cf: ResultSetExtractorでは、ResultSetの
next()
メソッドを使わないと、レコードの値を取得できない
- cf: ResultSetExtractorでは、ResultSetの
- →
while
文ではなく、do~while
文でループ処理う - プロジェクトフォルダの直下に
sample.csv
が作られる
- 以下の例では、
UserRowCallbackHandler.java
package com.example.demo.login.domain.repository.jdbc;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowCallbackHandler;
public class UserRowCallbackHandler implements RowCallbackHandler {
@Override
public void processRow(ResultSet rs) throws SQLException {
try {
//ファイル書き込みの準備
File file = new File("sample.csv");
FileWriter fw = new FileWriter(file.getAbsoluteFile());
BufferedWriter bw = new BufferedWriter(fw);
//取得件数分loop
do {
//ResultSetから値を取得してStringにセット
String str = rs.getString("user_id") + ","
+ rs.getString("password") + ","
+ rs.getString("user_name") + ","
+ rs.getDate("birthday") + ","
+ rs.getInt("age") + ","
+ rs.getBoolean("marriage") + ","
+ rs.getString("role");
//ファイルに書き込み&改行
bw.write(str);
bw.newLine();
} while(rs.next());
//強制的に書き込み&ファイルクローズ
bw.flush();
bw.close();
} catch (IOException e) {
e.printStackTrace();
throw new SQLException(e);
}
}
}
#UserDaoJdbcImpl
- RowCallbackHandlerを使ったCSV出力処理を追加
UserDaoJdbcImpl.java
//中略(全文は下記参考)
//SQL取得結果をサーバーにCSVで保存する
@Override
public void userCsvOut() throws DataAccessException {
// M_USERテーブルのデータを全件取得するSQL
String sql = "SELECT * FROM m_user";
// ResultSetExtractorの生成
UserRowCallbackHandler handler = new UserRowCallbackHandler();
//SQL実行&CSV出力
jdbc.query(sql, handler);
}
#サービスクラス
- ユーザー一覧をsample.csvファイルに出力する
- リポジトリークラス(UserDaoJdbcImpl)のCSV出力メソッドを呼び出す
dao.userCsvOut();
- リポジトリークラス(UserDaoJdbcImpl)のCSV出力メソッドを呼び出す
- メソッドファイルを取得するメソッド追加
- 引数で指定されたファイル名をサーバーから取得
FileSystem fs = FileSystems.getDefault();
Path p = fs.getPath(fileName);
- ファイルの中身をbyte型の配列にしてリターン
- 引数で指定されたファイル名をサーバーから取得
UserService.java
//中略(全文は下記参考)
// ユーザー一覧をCSV出力する.
public void userCsvOut() throws DataAccessException {
// CSV出力
dao.userCsvOut();
}
//サーバーに保存されているファイルを取得して、byte配列に変換する.
public byte[] getFile(String fileName) throws IOException {
// ファイルシステム(デフォルト)の取得
FileSystem fs = FileSystems.getDefault();
// ファイル取得
Path p = fs.getPath(fileName);
// ファイルをbyte配列に変換
byte[] bytes = Files.readAllBytes(p);
return bytes;
}
#コントローラクラス
###CSV出力メソッド追加
- サービスクラスのCSV出力とファイル取得メソッドを呼び出し、HTTPヘッダーの値をセットして返す
header.add("Content-Type", "text/csv; charset=UTF-8");
header.setContentDispositionFormData("filename", "sample.csv");
- メソッドの戻り値を
ResponseEntity
型にすると、Thymeleafのテンプレート(html)ではなく、ファイル(byte型の配列)を呼び出し元に返却できる
HomeController.java
//中略(全文は下記参考)
/**
* ユーザー一覧のCSV出力用処理.
*/
@GetMapping("/userList/csv")
public ResponseEntity<byte[]> getUserListCsv(Model model) {
//ユーザーを全件取得して、CSVをサーバーに保存する
userService.userCsvOut();
byte[] bytes = null;
try {
//サーバーに保存されているsample.csvファイルをbyteで取得する
bytes = userService.getFile("sample.csv");
} catch (IOException e) {
e.printStackTrace();
}
//HTTPヘッダーの設定
HttpHeaders header = new HttpHeaders();
header.add("Content-Type", "text/csv; charset=UTF-8");
header.setContentDispositionFormData("filename", "sample.csv");
//sample.csvを戻す
return new ResponseEntity<>(bytes, header, HttpStatus.OK);
}
#SpringBootを起動してホーム画面確認!
- http://localhost:8080/home
- ユーザー一覧に移りCSV出力ボタンを押すと、プロジェクトフォルダー直下にsample.csvがダウンロードされる
- 中身を見ると、ユーザーマスタテーブルの内容が出力されている事が確認できました〜〜^^
#(参考)コード全文
UserDaoJdbcImpl.java
package com.example.demo.login.domain.repository.jdbc;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import com.example.demo.login.domain.model.User;
import com.example.demo.login.domain.repository.UserDao;
@Repository("UserDaoJdbcImpl")
public class UserDaoJdbcImpl implements UserDao {
@Autowired
JdbcTemplate jdbc;
// Userテーブルの件数を取得.
@Override
public int count() throws DataAccessException {
//全件取得してカウント
int count = jdbc.queryForObject("SELECT COUNT(*) FROM m_user", Integer.class);
return count;
}
// Userテーブルにデータを1件insert.
@Override
public int insertOne(User user) throws DataAccessException {
//1件登録
int rowNumber = jdbc.update("INSERT INTO m_user(user_id,"
+ " password,"
+ " user_name,"
+ " birthday,"
+ " age,"
+ " marriage,"
+ " role)"
+ " VALUES(?, ?, ?, ?, ?, ?, ?)",
user.getUserId(),
user.getPassword(),
user.getUserName(),
user.getBirthday(),
user.getAge(),
user.isMarriage(),
user.getRole());
return rowNumber;
}
// Userテーブルのデータを1件取得
@Override
public User selectOne(String userId) throws DataAccessException {
// 1件取得
Map<String, Object> map = jdbc.queryForMap("SELECT * FROM m_user"
+ " WHERE user_id = ?", userId);
// 結果返却用の変数
User user = new User();
// 取得したデータを結果返却用の変数にセットしていく
user.setUserId((String) map.get("user_id")); //ユーザーID
user.setPassword((String) map.get("password")); //パスワード
user.setUserName((String) map.get("user_name")); //ユーザー名
user.setBirthday((Date) map.get("birthday")); //誕生日
user.setAge((Integer) map.get("age")); //年齢
user.setMarriage((Boolean) map.get("marriage")); //結婚ステータス
user.setRole((String) map.get("role")); //ロール
return user;
}
// Userテーブルの全データを取得.
@Override
public List<User> selectMany() throws DataAccessException {
// M_USERテーブルのデータを全件取得
List<Map<String, Object>> getList = jdbc.queryForList("SELECT * FROM m_user");
// 結果返却用の変数
List<User> userList = new ArrayList<>();
// 取得したデータを結果返却用のListに格納していく
for (Map<String, Object> map : getList) {
//Userインスタンスの生成
User user = new User();
// Userインスタンスに取得したデータをセットする
user.setUserId((String) map.get("user_id")); //ユーザーID
user.setPassword((String) map.get("password")); //パスワード
user.setUserName((String) map.get("user_name")); //ユーザー名
user.setBirthday((Date) map.get("birthday")); //誕生日
user.setAge((Integer) map.get("age")); //年齢
user.setMarriage((Boolean) map.get("marriage")); //結婚ステータス
user.setRole((String) map.get("role")); //ロール
//結果返却用のListに追加
userList.add(user);
}
return userList;
}
// Userテーブルを1件更新.
@Override
public int updateOne(User user) throws DataAccessException {
//1件更新
int rowNumber = jdbc.update("UPDATE M_USER"
+ " SET"
+ " password = ?,"
+ " user_name = ?,"
+ " birthday = ?,"
+ " age = ?,"
+ " marriage = ?"
+ " WHERE user_id = ?",
user.getPassword(),
user.getUserName(),
user.getBirthday(),
user.getAge(),
user.isMarriage(),
user.getUserId());
return rowNumber;
}
// Userテーブルを1件削除.
@Override
public int deleteOne(String userId) throws DataAccessException {
//1件削除
int rowNumber = jdbc.update("DELETE FROM m_user WHERE user_id = ?", userId);
return rowNumber;
}
//SQL取得結果をサーバーにCSVで保存する
@Override
public void userCsvOut() throws DataAccessException {
// M_USERテーブルのデータを全件取得するSQL
String sql = "SELECT * FROM m_user";
// ResultSetExtractorの生成
UserRowCallbackHandler handler = new UserRowCallbackHandler();
//SQL実行&CSV出力
jdbc.query(sql, handler);
}
}
UserService.java
package com.example.demo.login.domain.service;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;
import com.example.demo.login.domain.model.User;
import com.example.demo.login.domain.repository.UserDao;
@Service
public class UserService {
@Autowired
@Qualifier("UserDaoJdbcImpl4")
UserDao dao;
/**
* insert用メソッド.
*/
public boolean insert(User user) {
// insert実行
int rowNumber = dao.insertOne(user);
// 判定用変数
boolean result = false;
if (rowNumber > 0) {
// insert成功
result = true;
}
return result;
}
/**
* カウント用メソッド.
*/
public int count() {
return dao.count();
}
/**
* 全件取得用メソッド.
*/
public List<User> selectMany() {
// 全件取得
return dao.selectMany();
}
/**
* 1件取得用メソッド.
*/
public User selectOne(String userId) {
// selectOne実行
return dao.selectOne(userId);
}
/**
* 1件更新用メソッド.
*/
public boolean updateOne(User user) {
// 判定用変数
boolean result = false;
// 1件更新
int rowNumber = dao.updateOne(user);
if (rowNumber > 0) {
// update成功
result = true;
}
return result;
}
/**
* 1件削除用メソッド.
*/
public boolean deleteOne(String userId) {
// 1件削除
int rowNumber = dao.deleteOne(userId);
// 判定用変数
boolean result = false;
if (rowNumber > 0) {
// delete成功
result = true;
}
return result;
}
// ユーザー一覧をCSV出力する.
public void userCsvOut() throws DataAccessException {
// CSV出力
dao.userCsvOut();
}
/**
* サーバーに保存されているファイルを取得して、byte配列に変換する.
*/
public byte[] getFile(String fileName) throws IOException {
// ファイルシステム(デフォルト)の取得
FileSystem fs = FileSystems.getDefault();
// ファイル取得
Path p = fs.getPath(fileName);
// ファイルをbyte配列に変換
byte[] bytes = Files.readAllBytes(p);
return bytes;
}
}
HomeController.java
package com.example.demo.login.controller;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import com.example.demo.login.domain.model.SignupForm;
import com.example.demo.login.domain.model.User;
import com.example.demo.login.domain.service.UserService;
@Controller
public class HomeController {
@Autowired
UserService userService;
//結婚ステータスのラジオボタン用変数
private Map<String, String> radioMarriage;
/**
* ラジオボタンの初期化メソッド(ユーザー登録画面と同じ).
*/
private Map<String, String> initRadioMarrige() {
Map<String, String> radio = new LinkedHashMap<>();
// 既婚、未婚をMapに格納
radio.put("既婚", "true");
radio.put("未婚", "false");
return radio;
}
/**
* ホーム画面のGET用メソッド
*/
@GetMapping("/home")
public String getHome(Model model) {
//コンテンツ部分にユーザー詳細を表示するための文字列を登録
model.addAttribute("contents", "login/home :: home_contents");
return "login/homeLayout";
}
/**
* ユーザー一覧画面のGETメソッド用処理.
*/
@GetMapping("/userList")
public String getUserList(Model model) {
//コンテンツ部分にユーザー一覧を表示するための文字列を登録
model.addAttribute("contents", "login/userList :: userList_contents");
//ユーザー一覧の生成
List<User> userList = userService.selectMany();
//Modelにユーザーリストを登録
model.addAttribute("userList", userList);
//データ件数を取得
int count = userService.count();
model.addAttribute("userListCount", count);
return "login/homeLayout";
}
/**
* ユーザー詳細画面のGETメソッド用処理.
*/
@GetMapping("/userDetail/{id:.+}")
public String getUserDetail(@ModelAttribute SignupForm form,
Model model,
@PathVariable("id") String userId) {
// ユーザーID確認(デバッグ)
System.out.println("userId = " + userId);
// コンテンツ部分にユーザー詳細を表示するための文字列を登録
model.addAttribute("contents", "login/userDetail :: userDetail_contents");
// 結婚ステータス用ラジオボタンの初期化
radioMarriage = initRadioMarrige();
// ラジオボタン用のMapをModelに登録
model.addAttribute("radioMarriage", radioMarriage);
// ユーザーIDのチェック
if (userId != null && userId.length() > 0) {
// ユーザー情報を取得
User user = userService.selectOne(userId);
// Userクラスをフォームクラスに変換
form.setUserId(user.getUserId()); //ユーザーID
form.setUserName(user.getUserName()); //ユーザー名
form.setBirthday(user.getBirthday()); //誕生日
form.setAge(user.getAge()); //年齢
form.setMarriage(user.isMarriage()); //結婚ステータス
// Modelに登録
model.addAttribute("signupForm", form);
}
return "login/homeLayout";
}
/**
* ユーザー更新用処理.
*/
@PostMapping(value = "/userDetail", params = "update")
public String postUserDetailUpdate(@ModelAttribute SignupForm form,
Model model) {
System.out.println("更新ボタンの処理");
//Userインスタンスの生成
User user = new User();
//フォームクラスをUserクラスに変換
user.setUserId(form.getUserId());
user.setPassword(form.getPassword());
user.setUserName(form.getUserName());
user.setBirthday(form.getBirthday());
user.setAge(form.getAge());
user.setMarriage(form.isMarriage());
//更新実行
boolean result = userService.updateOne(user);
if (result == true) {
model.addAttribute("result", "更新成功");
} else {
model.addAttribute("result", "更新失敗");
}
//ユーザー一覧画面を表示
return getUserList(model);
}
/**
* ユーザー削除用処理.
*/
@PostMapping(value = "/userDetail", params = "delete")
public String postUserDetailDelete(@ModelAttribute SignupForm form,
Model model) {
System.out.println("削除ボタンの処理");
//削除実行
boolean result = userService.deleteOne(form.getUserId());
if (result == true) {
model.addAttribute("result", "削除成功");
} else {
model.addAttribute("result", "削除失敗");
}
//ユーザー一覧画面を表示
return getUserList(model);
}
/**
* ログアウト用処理.
*/
@PostMapping("/logout")
public String postLogout() {
//ログイン画面にリダイレクト
return "redirect:/login";
}
/**
* ユーザー一覧のCSV出力用処理.
*/
@GetMapping("/userList/csv")
public ResponseEntity<byte[]> getUserListCsv(Model model) {
//ユーザーを全件取得して、CSVをサーバーに保存する
userService.userCsvOut();
byte[] bytes = null;
try {
//サーバーに保存されているsample.csvファイルをbyteで取得する
bytes = userService.getFile("sample.csv");
} catch (IOException e) {
e.printStackTrace();
}
//HTTPヘッダーの設定
HttpHeaders header = new HttpHeaders();
header.add("Content-Type", "text/csv; charset=UTF-8");
header.setContentDispositionFormData("filename", "sample.csv");
//sample.csvを戻す
return new ResponseEntity<>(bytes, header, HttpStatus.OK);
}
}