SpringBoot バリデーション

SpringBootのバリデータを試す

・@NotBlank付与

import java.io.Serializable;
import javax.validation.constraints.NotBlank;

public class User implements Serializable {
    
    private static final long serialVersionUID = 1L;

    @NotBlank
    private String name;

    @NotBlank
    private String password;

    public String getName() {
        return name;
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

・バリデータのメッセージを設定

// src/main/resources/ValidationMessages.properties
javax.validation.constraints.NotBlank.message        = 入力してください。

・@Valid付与、BindingResultもセットで必要

    @RequestMapping(value="/", method=RequestMethod.GET)
    public ModelAndView index(ModelAndView mv) {
        mv.addObject("user", new User());
        mv.setViewName("index");
        return mv;
    }

    @RequestMapping(value="/regist", method=RequestMethod.POST)
    public ModelAndView regist(@ModelAttribute @Valid User user, BindingResult result, ModelAndView mv) {
        if (result.hasErrors()) {
            mv.setViewName("index");
            mv.addObject("user", user);
            return mv;
        }

        mv.setViewName("result");
        mv.addObject("user", user);
        return mv;
    }

・バリデーションメッセージの表示制御

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <form method="post" action="regist" th:object="${user}" >
        <label for="name">名前:</label>
        <input type="text" name="name" th:field="*{name}" th:errorclass="error-field">
        <span th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span><br>
        <label for="password">パスワード:</label>
        <input type="text" name="password" th:field="*{password}" th:errorclass="error-field">
        <span th:if="${#fields.hasErrors('password')}" th:errors="password"></span>
        <input type="submit" value="送信" />
    </form>
</body>
</html>

SpringBoot レスポンス

SpringBoot レスポンスについて

・テンプレート名返却
※@RestControllerではbodyとして返却される

@RequestMapping(value="/", method=RequestMethod.GET)
public String index() {
    return "index";
}

・body返却

@RequestMapping(value="/", method=RequestMethod.GET)
@ResponseBody
public String index() {
    return "index";
}

・ModelAndView返却

@RequestMapping(value="/", method=RequestMethod.GET)
public ModelAndView index(ModelAndView mv) {
    mv.setStatus(HttpStatus.OK);
    mv.setViewName("index");
    return mv;
}

フォワード

@RequestMapping(value="/", method=RequestMethod.GET)
public String index() {
    return "forward:method";
}

@RequestMapping(value="/method", method=RequestMethod.GET)
@ResponseBody
public String method() {
    return "method";
}

・リダイレクト

@RequestMapping(value="/", method=RequestMethod.GET)
public String index() {
    return "redirect:method";
    // return "redirect:http://localhost:8080/method";
}

@RequestMapping(value="/method", method=RequestMethod.GET)
@ResponseBody
public String method() {
    return "method";
}

・リソース返却

@Autowired
ResourceLoader resourceLoader;

@RequestMapping(value="/", method=RequestMethod.GET, produces=MediaType.TEXT_HTML_VALUE)
public Resource index() {
    return resourceLoader.getResource("classpath:templates/index.html");
}

xml返却(@ResponseBody不要)

@RequestMapping(value="/", method=RequestMethod.GET, produces=MediaType.APPLICATION_XML_VALUE)
public String index() {
    return "<xml><title>index</title></xml>";
}

・HTTPヘッダやステータスとあわせて返却

@RequestMapping(value="/", method=RequestMethod.GET)
public ResponseEntity<String> index() {
    String body = "index";
    HttpHeaders headers = new HttpHeaders();
    headers.add(HttpHeaders.DATE, LocalDateTime.now().toString());
    return new ResponseEntity<String>(body, headers, HttpStatus.OK);
}

SpringBoot jsonの受け取りと返却

jsonの受け取りと返却を試した

ajaxからjsonリクエストを飛ばす

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>User Setting</title>
<script
    src="https://code.jquery.com/jquery-3.4.0.min.js"
    integrity="sha256-BJeo0qm959uMBGb65z40ejJYGSgR7REI4+CW1fNKwOg="
    crossorigin="anonymous"></script>
<script>
$(function() {
    $('#submit').on('click',function(){
        var json = {
                'name':$('#name').val(),
                'password':$('#password').val()
            };
        // リストの場合[{json1},{json2}]...
        $.ajax({
            url:'/json',
            type:'POST',
            contentType: 'application/json',
            data:JSON.stringify(json),
            dataType:'json'
        })
        .done((data, textStatus, jqXHR) => {
            alert(JSON.stringify(data));
        })
        .fail((jqXHR, textStatus, errorThrown) => {
            alert(jqXHR.status);
        })
        .always((data) => {
        });        
    });
});
</script>
</head>
<body>
    <form method="post" action="/body">
        <label for="name">名前:</label>
        <input type="text" name="name" id="name"><br>
        <label for="password">パスワード:</label>
        <input type="text" name="password" id="password">
        <input type="button" id="submit" value="送信" />
    </form>
</body>
</html>

jsonのデータ格納先

public class User implements Serializable {
    
    private static final long serialVersionUID = 1L;

    private String name;

    private String password;

    public String getName() {
        return name;
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }    
}

コントローラ

    @RequestMapping(value= {"/json"}, method=RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public User index(@RequestBody User user) {
        return user;
    }
    // リストの場合
    @RequestMapping(value= {"/json"}, method=RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public List<User> index(@RequestBody List<User> users) {
        return users;
    }

SpringBoot パラメータ取得

・リクエストパラメータ(クエリパラメータ、POSTパラメータ)の取得
value(name):パラメータ名
required:必須フラグ(デフォルトtrue)
defaultValue:デフォルト値

// http://localhost:8080/?id=1

// value省略(変数名がパラメータと一致していれば省略可能)
public String getUser(@RequestParam String id) {
    return id;
}

// value省略
public String getUser(@RequestParam("id") String id) {
    return id;
}

// value指定
public String getUser(@RequestParam(value="id") String id) {
    return id;
}

// 必須フラグ指定
public String getUser(@RequestParam(value="id", required=true) String id) {
    return id;
}

// デフォルト指定
public String getUser(@RequestParam(value="id", required=true, defaultValue="") String id) {
    return id;
}

// Optional
public String getUser(@RequestParam(value="id", required=false) Optional<String> id) {
    return id.orElse("");
}

・URLパスパラメータの取得
value(name):URLパスパラメータ名
required:必須フラグ(デフォルトtrue)

// http://localhost:8080/1

// value省略(変数名がURLパスパラメータと一致していれば省略可能)
@RequestMapping(value= {"/{id}"}, method=RequestMethod.GET)
@ResponseBody
public String getUser(@PathVariable String id) {
    return id;
}

// value省略
@RequestMapping(value= {"/{id}"}, method=RequestMethod.GET)
@ResponseBody
public String getUser(@PathVariable("id") String id) {
    return id;
}

// value指定
@RequestMapping(value= {"/{id}"}, method=RequestMethod.GET)
@ResponseBody
public String getUser(@PathVariable(value="id") String id) {
    return id;
}

// URLパスパラメータを必須としない場合、リクエストマッピングにURLパスパラメータを外したURLを追加する必要がある
@RequestMapping(value= {"/", "/{id}"}, method=RequestMethod.GET)
@ResponseBody
public String getUser(@PathVariable(value="id", required=false) String id) {
    return id;
}

・リクエストヘッダ
value(name):リクエストヘッダの項目名
required:必須フラグ(デフォルトtrue)
defaultValue:デフォルト値

// ex Mozilla/5.0~
@RequestMapping(value= {"/"}, method=RequestMethod.GET)
@ResponseBody
public String index(@RequestHeader("User-Agent") String userAgent) {
    return userAgent;
}

・リクエストボディ
required:必須フラグ(デフォルトtrue)

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>User Setting</title>
</head>
<body>
    <form method="post" action="/body">
        <label for="name">名前:</label>
        <input type="text" name="name"><br>
        <label for="password">パスワード:</label>
        <input type="text" name="password">
        <input type="submit" value="送信" />
    </form>
</body>
</html>
// ex name=name&password=password
@RequestMapping(value= {"/body"}, method=RequestMethod.POST)
@ResponseBody
public String index(@RequestBody String body) {
    return body;
}

SpringBoot リクエストマッピング

・リクエスマッピングは以下の条件を指定できる
 value(path)
  リクエストパス

 method
  HTTPメソッドによる絞り込み

 params
  リクエストパラメータによる絞り込み

 headers
  リクエストヘッダによる絞り込み

 consumes
  Content-Typeヘッダによる絞り込み

 produces
  Acceptヘッダによる絞り込み


・HTTPメソッドのマッピング確認

//@RestController
//@RequestMapping("api/users")
@Controller
public class UserController {

    //@GetMapping("/")
    @RequestMapping(value="/", method=RequestMethod.GET)
    public ModelAndView index(ModelAndView mv) {
        mv.setViewName("index");
        return mv;
    }
    
    //@PostMapping
    @RequestMapping(method=RequestMethod.POST)
    @ResponseBody
    public String createUser(@RequestParam("name") String name) {
        return "call post";
    }
    
    //@GetMapping("/{id}")
    @RequestMapping(value="/{id}", method=RequestMethod.GET)
    @ResponseBody
    public String getUser(@PathVariable String id) {
        return "call get";
    }

    //@PutMapping("/{id}")
    @RequestMapping(value="/{id}", method=RequestMethod.PUT)
    @ResponseBody
    public String updateUser(@PathVariable String id, @RequestParam String name, @RequestParam String password) {
        return "call put";
    }
    
    //@DeleteMapping("/{id}")
    @RequestMapping(value="/{id}", method=RequestMethod.DELETE)
    @ResponseBody
    public String deleteUser(@PathVariable String id) {
        return "call delete";
    }
}

・PostMapping

<form method="post" action="/">
    <label for="name">名前:</label>
    <input type="text" name="name"><br>
    <label for="password">パスワード:</label>
    <input type="text" name="password">
    <input type="submit" value="送信" />
</form>

・GetMapping

<form method="get" action="/1">
    <label for="name">名前:</label>
    <input type="text" name="name"><br>
    <label for="password">パスワード:</label>
    <input type="text" name="password">
    <input type="submit" value="送信" />
</form>

・PutMapping

<form method="post" action="/1">
    <label for="name">名前:</label>
    <input type="text" name="name"><br>
    <label for="password">パスワード:</label>
    <input type="text" name="password">
    <input type="hidden" name="_method" value="PUT">
    <input type="submit" value="送信" />
</form>

・DeleteMapping

<form method="post" action="/1">
    <label for="name">名前:</label>
    <input type="text" name="name"><br>
    <label for="password">パスワード:</label>
    <input type="text" name="password">
    <input type="hidden" name="_method" value="DELETE">
    <input type="submit" value="送信" />
</form>

Java ファイル操作

ファイル操作について調べてみた

インスタンスの生成

Path path = Paths.get("C:\\test.txt");
System.out.println(path);

File file = new File("C:\\test.txt");
Path path = file.toPath();
System.out.println(path);

・パス指定

// 絶対パス
Path path = Paths.get("C:\\test.txt");
System.out.println(path);
System.out.println(path.isAbsolute()); // true

// 相対パス
String dir = System.getProperty("user.dir");
System.out.println(dir);
Path path = Paths.get("test.txt");
System.out.println(path.toAbsolutePath()); // dir/path
System.out.println(path.isAbsolute()); // false

// 正規化
Path path = Paths.get("C:\\dir1", "..\\dir2", "test.txt");
System.out.println(path);             // C:\dir1\..\dir2\test.txt
System.out.println(path.normalize()); // C:\dir2\test.txt

・存在チェック file directory共通

Path path = Paths.get("C:\\dir");
System.out.println(Files.exists(path));
System.out.println(Files.notExists(path));

・更新日時/ファイルサイズの取得

Path path = Paths.get("C:\\test.txt");
System.out.println(Files.getLastModifiedTime(path));
System.out.println(Files.size(path));
// 対象が存在しない場合 NoSuchFileException

ディレクトリ判定

Path path = Paths.get("C:\\dir");
System.out.println(Files.isDirectory(path)); // ディレクトリであればtrue
// 対象が存在しない場合 false

ディレクトリの作成

Path path = Paths.get("C:\\dir1\\dir2");
Files.createDirectory(path);   // 親ディレクトリがない場合 NoSuchFileException
Files.createDirectories(path); // 親ディレクトリがない場合 作成
// ディレクトリが既に存在しても例外は起きない

・ファイルの作成

Path path = Paths.get("C:\\dir\\test.txt");
System.out.println(Files.createFile(path));
// 親ディレクトリがない場合 NoSuchFileException
// ファイルが既に存在する場合 FileAlreadyExistsException

・ファイルの読み込み

Path path = Paths.get("C:\\dir\\test.txt");

// バイト
byte[] bytes = Files.readAllBytes(path);

// 文字列
Files.readAllLines(path).stream().forEach(System.out::println); 

// バッファリング
BufferedReader reader = Files.newBufferedReader(path);
reader.lines().forEach(System.out::println);
// 対象が存在しない場合いずれも NoSuchFileException

・ファイルの書き込み

Path path = Paths.get("C:\\dir\\test.txt");
String line = "line";

Files.write(path, line.getBytes());

// バッファリング
BufferedWriter writer = Files.newBufferedWriter(path);
writer.write(line, 0, line.length());
writer.newLine();
writer.flush();

// ディレクトリが存在しない場合 NoSuchFileException
// ファイルが存在しない場合 ファイルが作成される

・ファイルのコピー

Path src = Paths.get("C:\\dir\\test1.txt");
Path dest = Paths.get("C:\\dir\\test2.txt");

Files.copy(src, dest); // コピー先が存在する場合 FileAlreadyExistsException
Files.copy(src, dest, StandardCopyOption.REPLACE_EXISTING); // コピー先が存在する場合上書き
// コピー元が存在しない場合 NoSuchFileException

・ファイルの移動

Path src = Paths.get("C:\\dir\\test1.txt");
Path dest = Paths.get("C:\\dir\\test2.txt");

Files.move(src, dest); // 移動先が存在する場合 FileAlreadyExistsException
Files.move(src, dest, StandardCopyOption.REPLACE_EXISTING); // 移動先が存在する場合上書き
// 移動元が存在しない場合 NoSuchFileException

・ファイルの削除

Path path = Paths.get("C:\\dir1\\test1.txt");

Files.delete(path); // 削除対象が存在しない場合 NoSuchFileException
Files.deleteIfExists(path); // 削除に成功した場合にtrue返却 削除対象がなくても例外は発生しない

・ファイル検索

Path path = Paths.get("C:\\dir");

// 所定ディレクトリ内の検索
Files.list(path).forEach(System.out::println); // パスがディレクトリでない場合 NotDirectoryException

// 再帰検索
Files.walk(path).forEach(System.out::println); // パスがファイルでも動作

// 条件付き検索
Files.find(path, Integer.MAX_VALUE, (i, j) -> i.toString().contains("txt")).forEach(System.out::println); // パスがファイルでも動作

// ディレクトリが存在しない場合 NoSuchFileException

・テンポラリ作成

Path path = Paths.get("C:\\dir");

// 一時ディレクトリ
Files.createTempDirectory(path, "tmp_"); // ex tmp_9194118049955059353

// 一時ファイル
Files.createTempFile(path, "tmp_", ".csv"); // ex tmp_2871683714486788849.csv

// ディレクトリが存在しないやファイルを指定した場合 NoSuchFileException

Java 日時APIについて

日時APIについて調べてみた

・日時クラス

// LocalDateTime タイムゾーンなし
System.out.println(LocalDateTime.now()); // ex 2019-04-12T23:41:14.816

// ZonedDateTime タイムゾーン付き
System.out.println(ZonedDateTime.now()); // ex 2019-04-12T23:42:07.975+09:00[GMT+09:00]

// OffsetDateTime オフセット付き
System.out.println(OffsetDateTime.now()); // ex 2019-04-12T23:42:36.609+09:00

インスタンス生成

// now 現在日時
System.out.println(LocalDateTime.now());

// of
System.out.println(LocalDateTime.of(9999, 12, 31, 23, 59, 59, 999999999)); // ex 9999-12-31T23:59:59.999999999

・日時クラス間の変換

// atZone
System.out.println(LocalDateTime.now().atZone(ZoneOffset.systemDefault()));

// atOffset
System.out.println(LocalDateTime.now().atOffset(ZoneOffset.ofHours(9)));

// toInstant
System.out.println(LocalDateTime.now().toInstant(ZoneOffset.ofHours(9)));

// LocalDate
System.out.println(LocalDateTime.now().toLocalDate()); // ex 2019-04-12

// LocalTime
System.out.println(LocalDateTime.now().toLocalTime()); // 23:45:02.682

// ofInstant
Date date = new Date();
System.out.println(LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()));

// from
System.out.println(LocalDateTime.from(ZonedDateTime.now()));

System.out.println(LocalDateTime.from(OffsetDateTime.now()));

・日時クラスのパラメータ

// ZoneId
System.out.println(ZoneId.of("Asia/Tokyo"));
System.out.println(ZoneId.systemDefault()); // ex GMT+09:00

// ZoneOffset
System.out.println(ZoneOffset.ofHours(9));

// isSupported ChronoField フィールドの標準セット
System.out.println(LocalDateTime.now().isSupported(ChronoField.YEAR));
System.out.println(LocalDateTime.now().isSupported(ChronoField.MONTH_OF_YEAR));
System.out.println(LocalDateTime.now().isSupported(ChronoField.DAY_OF_MONTH));
System.out.println(LocalDateTime.now().isSupported(ChronoField.HOUR_OF_DAY));
System.out.println(LocalDateTime.now().isSupported(ChronoField.MINUTE_OF_HOUR));
System.out.println(LocalDateTime.now().isSupported(ChronoField.SECOND_OF_MINUTE));
System.out.println(LocalDateTime.now().isSupported(ChronoField.NANO_OF_SECOND));
System.out.println(LocalDateTime.now().isSupported(ChronoField.DAY_OF_WEEK));

// range
System.out.println(LocalDateTime.now().range(ChronoField.YEAR));              // -999999999 - 999999999
System.out.println(LocalDateTime.now().range(ChronoField.MONTH_OF_YEAR));    // 1 - 12
System.out.println(LocalDateTime.now().range(ChronoField.DAY_OF_MONTH));     // 1 - 30(31では?)
System.out.println(LocalDateTime.now().range(ChronoField.HOUR_OF_DAY));      // 0 - 23
System.out.println(LocalDateTime.now().range(ChronoField.MINUTE_OF_HOUR));   // 0 - 59
System.out.println(LocalDateTime.now().range(ChronoField.SECOND_OF_MINUTE)); // 0 - 59
System.out.println(LocalDateTime.now().range(ChronoField.NANO_OF_SECOND));   // 0 - 999999999
System.out.println(LocalDateTime.now().range(ChronoField.DAY_OF_WEEK));      // 1 - 7

// isSupported ChronoUnit 日時期間の単位の標準セット
System.out.println(LocalDateTime.now().isSupported(ChronoUnit.YEARS));
System.out.println(LocalDateTime.now().isSupported(ChronoUnit.MONTHS));
System.out.println(LocalDateTime.now().isSupported(ChronoUnit.DAYS));
System.out.println(LocalDateTime.now().isSupported(ChronoUnit.HOURS));
System.out.println(LocalDateTime.now().isSupported(ChronoUnit.MINUTES));
System.out.println(LocalDateTime.now().isSupported(ChronoUnit.SECONDS));
System.out.println(LocalDateTime.now().isSupported(ChronoUnit.NANOS));
System.out.println(LocalDateTime.now().isSupported(ChronoUnit.WEEKS));

・値の取得

System.out.println(LocalDateTime.now().getYear());
System.out.println(LocalDateTime.now().getMonth());//ex APRIL
System.out.println(LocalDateTime.now().getMonthValue()); // 1-12
System.out.println(LocalDateTime.now().getDayOfMonth());
System.out.println(LocalDateTime.now().getHour());
System.out.println(LocalDateTime.now().getMinute());
System.out.println(LocalDateTime.now().getSecond());
System.out.println(LocalDateTime.now().getNano());
System.out.println(LocalDateTime.now().getDayOfWeek());
System.out.println(LocalDateTime.now().getDayOfYear()); // 1-365

// get/getLong フィールド指定
System.out.println(LocalDateTime.now().get(ChronoField.YEAR));     // int
System.out.println(LocalDateTime.now().getLong(ChronoField.YEAR)); // long

// format フォーマット指定
System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy")));

// truncatedTo 切り捨て指定、指定したunitまで取得
System.out.println(LocalDateTime.now().truncatedTo(ChronoUnit.DAYS));

// until
LocalDateTime tomorrow = LocalDateTime.now().plusDays(1);
System.out.println(LocalDateTime.now().until(tomorrow, ChronoUnit.DAYS)); // 1

・比較

// compareTo ※エポック比較ではない
LocalDateTime now = LocalDateTime.now();
System.out.println(tomorrow.compareTo(now)); // 1
System.out.println(now.compareTo(tomorrow)); // -1
System.out.println(now.compareTo(now)); // 0

// equals ※エポック比較ではない
// 属性判定
System.out.println(now.equals(now)); // true
System.out.println(tomorrow.equals(now)); // false

// isAfter
// エポック比較
System.out.println(tomorrow.isAfter(now)); // true
System.out.println(now.isAfter(tomorrow)); // false

// isBefore
// エポック比較
System.out.println(now.isBefore(tomorrow)); // true
System.out.println(tomorrow.isBefore(now)); // false

// isEqual
// エポック比較
System.out.println(now.isEqual(now)); // true
System.out.println(tomorrow.isBefore(now)); // false

・編集

// plus
System.out.println(LocalDateTime.now().plusDays(1));

// minus
System.out.println(LocalDateTime.now().minusDays(1));

// with(set)
System.out.println(LocalDateTime.now().withDayOfMonth(1));