Spring BootでMybatisを使ってみる
GitHub:https://github.com/n-yata/mybatis-sample.git
pom.xml
pom.xmlは下のような感じ。
mybatis-spring-boot-starterを入れる。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.5</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>example</groupId> <artifactId>Mybatis-sample</artifactId> <version>0.0.1-SNAPSHOT</version> <name>Mybatis-sample</name> <description>Spring sample project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
application.properties
DBの接続情報とmybatisの設定を書く。
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres spring.datasource.username=postgres spring.datasource.password=postgres spring.datasource.driver-class-name=org.postgresql.Driver # MybatisマッピングJavaクラスのパッケージ mybatis.type-aliases-package=example.mapper # xmlマッピングファイルの場所 mybatis.mapper-locations=classpath:mapper/*.xml # アンスコ→キャメルケースに変換 mybatis.configuration.map-underscore-to-camel-case=true
propertiesに書いたJavaファイルとxmlファイルのパスは下のような感じ。
Modelクラス
User.java
Lombok便利。フィールドだけ書いて簡単実装。
package example.model; import lombok.Data; @Data public class User { private String userId; private String firstName; private String lastName; private String password; private String roleName; }
対応するテーブルのcreate文(PostgreSQLを使用)
CREATE TABLE IF NOT EXISTS usr( user_id VARCHAR(255) NOT NULL, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, role_name VARCHAR(255) NOT NULL, PRIMARY KEY (user_id) );
適当にインサート
INSERT INTO usr (user_id, first_name, last_name, role_name, password) VALUES ('test01', '太郎', '山田', 'USER', 'password'); INSERT INTO usr (user_id, first_name, last_name, role_name, password) VALUES ('test02', '花子', '田中', 'USER', 'password'); INSERT INTO usr (user_id, first_name, last_name, role_name, password) VALUES ('test03', '次郎', '佐藤', 'USER', 'password');
JavaとxmlファイルのMapper
SampleMybatis.java
インタフェースを書く。@Mapperアノテーション忘れずに。
package example.mapper; import java.util.List; import org.apache.ibatis.annotations.Mapper; import example.model.User; @Mapper public interface SampleMybatis { List<User> selectAll(); }
SampleMybatis.xml
Javaに書いたメソッドに対応するSQLを書く。
mapperタグのnamespace属性はパッケージ名.クラス名
selectタグ内のresultType属性でモデルクラス指定はパッケージ名.クラス名
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="example.mapper.SampleMybatis"> <select id="selectAll" resultType="example.model.User"> select * from usr </select> </mapper>
HomeController
Mapperインタフェース呼び出してSQL実行、画面に表示する。
package example.controller; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import example.mapper.SampleMybatis; import example.model.User; @Controller public class HomeController { @Autowired SampleMybatis mapper; @GetMapping(value = "/") public String get() { return "index"; } @PostMapping(value = "/") public String post(Model model) { List<User> userList = mapper.selectAll(); List<String> lines = new ArrayList<>(); for(User user : userList) { lines.add(user.getLastName() + " " + user.getFirstName() + ", " + user.getRoleName()); } model.addAttribute("file_contents", lines); return "index"; } }
index.html
ボタン押したらSQLの結果を表示
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <form method="post" action="/"> <input type="submit" value="selectAll"> </form> <table> <tr th:each="line:${file_contents}"> <td th:text=${line}></td> </tr> </table> </body> </html>
参考文献
https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
Spring Bootのapprication.propertiesを環境ごとに用意する
環境変数を使ってローカル環境、開発環境とかで読み込ませるpropertiesファイルを分ける方法。以下のようなファイルを用意する。
- application.properties 切り分け元
- application-*.properties 切り分け先
たとえばローカル環境、開発環境で分けるなら下のような感じ。(ファイル名、環境変数名は適当)
- application.properties
- application-develop.properties
- application-production.properties
application.properties
spring.profiles.active=${SPRING_PROFILES}
application-develop.properties
spring.datasource.url=ローカル環境の接続URL spring.datasource.username=ローカル環境のユーザ名 spring.datasource.password=ローカル環境のパスワード
application-production.properties
spring.datasource.url=開発環境の接続URL spring.datasource.username=開発環境のユーザ名 spring.datasource.password=開発環境のパスワード
あとは環境変数を各環境ごとに設定する。
上の例ならdevelop
とproduction
eclipseだったら「実行(起動)の構成」から環境変数を設定できる。
Spring BootでローカルからHeroku PostgreSQLに接続
application.properties
に下のように書く。
spring.datasource.url=jdbc:postgresql://<host>:<port>/<dbname>?sslmode=require&sslfactory=org.postgresql.ssl.NonValidatingFactory spring.datasource.username=<user> spring.datasource.password=<password>
<host><port><dbname><user><password>
は下のコマンドで確認できる
$ heroku pg:credentials:url
ちなみにHeroku上にアップしたアプリの場合、下記のように書けば
Heroku上に用意された環境変数から接続情報を取得できる。
spring.datasource.url=${JDBC_DATABASE_URL} spring.datasource.username=${JDBC_DATABASE_USERNAME} spring.datasource.password=${JDBC_DATABASE_PASSWORD}
Spring BootでTodoアプリ2 -Todo作成-
Todo画面の作成
前回ログイン機能を作成したので、次はメインとなるTodo画面の作成。
下記の記事を参考に作成、少し修正したり機能追加したり。
Spring BootでToDoアプリを作ってみた - かずきのBlog@hatena
Todo用テーブルの作成
前回作成したusrテーブルのuser_idを外部キーとして持つtodoテーブルを作成
DROP TABLE IF EXISTS todoitems CASCADE; CREATE TABLE IF NOT EXISTS todoitems( id SERIAL NOT NULL, title VARCHAR(255), done BOOLEAN, tododate TIMESTAMP NOT NULL, user_id VARCHAR(255) NOT NULL, PRIMARY KEY (id), FOREIGN KEY (user_id) REFERENCES usr );
Todo画面を作成
前回作成したtodo.htmlを修正する。
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <meta charset="UTF-8"> <title>Todo</title> </head> <body> <h2>Todo Spring App</h2> <p th:inline="text">login user: [[${lastName}]] [[${firstName}]]</p> <form th:action="@{/logout}" method="post"> <input type="submit" value="Sign Out" /> </form> <a th:unless="${todoItemForm.done}" th:href="@{/todo?isDone=true}">完了したアイテムの表示</a> <a th:if="${todoItemForm.done}" th:href="@{/todo?isDone=false}">TODOの表示</a> <hr /> <h3>TODOの追加</h3> <form method="post" th:action="@{/new}"> <input type="text" name="title" /> <input type="submit" value="追加" /> </form> <h3>TODOリスト</h3> <table> <thead> <tr> <th>Title</th> <th> <form th:if="${todoItemForm.done}" method="post" th:action="@{/deleteAll}"> <input type="hidden" name="done" th:value="true" /> <input type="submit" value="DeleteAll" /> </form> <form th:unless="${todoItemForm.done}" method="post" th:action="@{/doneAll}"> <input type="submit" value="DoneAll" /> </form> </th> </tr> <tbody> <tr th:each="todoItem : ${todoItemForm.todoItems}"> <td th:text="${todoItem.title}">xxx</td> <td> <form th:unless="${todoItemForm.done}" method="post" th:action="@{/done}" th:object="${todoItem}"> <input type="hidden" name="id" th:value="*{id}" /> <input type="submit" value="Done" /> </form> </td> <td> <form th:if="${todoItemForm.done}" method="post" th:action="@{/restore}" th:object="${todoItem}"> <input type="hidden" name="id" th:value="*{id}" /> <input type="submit" value="Restore" /> </form> </td> <td> <form th:if="${todoItemForm.done}" method="post" th:action="@{/delete}" th:object="${todoItem}"> <input type="hidden" name="id" th:value="*{id}" /> <input type="submit" value="Delete" /> </form> </td> </tr> </tbody> </table> </body> </html>
Javaを書いてく
前回作成したログイン機能のみのアプリに下記を加えていく。
/SpringTodo/src/main/java/nyata/app/todo
- HomeController.java
/SpringTodo/src/main/java/nyata/domain/model
- TodoItem.java
/SpringTodo/src/main/java/nyata/domain/repositoriy
- TodoItemRepository.java
/SpringTodo/src/main/java/nyata/domain/service
- TodoItemForm.java
HomeController.java
画面から操作された時のコントローラ。参照したり更新したり。
package nyata.app.todo; import java.time.LocalDateTime; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.stereotype.Controller; import org.springframework.transaction.annotation.Transactional; 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.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import nyata.domain.model.TodoItem; import nyata.domain.repositoriy.TodoItemRepository; import nyata.domain.service.TodoItemForm; import nyata.domain.service.TodoUserDetails; /** * Todo画面のコントローラ * @author nyata */ @Controller public class HomeController { @Autowired TodoItemRepository repository; /** * 一覧表示 * @param todoItemForm * @param isDone * @param userDetails * @param model */ @GetMapping(value = "/todo") public String todo(@ModelAttribute TodoItemForm todoItemForm, @RequestParam("isDone") Optional<Boolean> isDone, @AuthenticationPrincipal TodoUserDetails userDetails, Model model) { todoItemForm.setDone(isDone.isPresent() ? isDone.get() : false); todoItemForm.setTodoItems( this.repository.findByDoneAndUserOrderByTododateAsc(todoItemForm.isDone(), userDetails.getUser())); model.addAttribute("firstName", userDetails.getUser().getFirstName()); model.addAttribute("lastName", userDetails.getUser().getLastName()); return "todo"; } /** * 状態を「完了」に変更 * @param id */ @PostMapping(value = "/done") public String done(@RequestParam("id") long id) { TodoItem item = this.repository.getOne(id); item.setDone(true); this.repository.save(item); return "redirect:/todo?isDone=false"; } /** * すべてのアイテムの状態を「完了」に変更 * @param todoItemForm * @param userDetails */ @PostMapping(value = "/doneAll") public String doneAll(@ModelAttribute TodoItemForm todoItemForm, @AuthenticationPrincipal TodoUserDetails userDetails) { todoItemForm.setTodoItems(this.repository.findByDoneAndUser(false, userDetails.getUser())); for (TodoItem todoitem : todoItemForm.getTodoItems()) { todoitem.setDone(true); this.repository.save(todoitem); } return "redirect:/todo?isDone=false"; } /** * 状態を「未完」に変更 * @param id */ @PostMapping(value = "/restore") public String RestoreAction(@RequestParam("id") long id) { TodoItem item = this.repository.getOne(id); item.setDone(false); this.repository.save(item); return "redirect:/todo?isDone=true"; } /** * アイテムを削除 * @param id */ @PostMapping(value = "/delete") public String deleteItem(@RequestParam("id") long id) { this.repository.deleteById(id); return "redirect:/todo?isDone=true"; } /** * 「完了」状態のすべてのアイテムを削除 * @param done * @param userDetails */ @PostMapping(value = "/deleteAll") @Transactional public String deleteAll(@RequestParam("done") boolean done, @AuthenticationPrincipal TodoUserDetails userDetails) { this.repository.deleteByDoneAndUser(done, userDetails.getUser()); return "redirect:/todo?isDone=true"; } /** * 新規アイテムの登録 * @param item * @param userDetails */ @PostMapping(value = "/new") public String newItem(TodoItem item, @AuthenticationPrincipal TodoUserDetails userDetails) { item.setDone(false); item.setTododate(LocalDateTime.now()); item.setUser(userDetails.getUser()); this.repository.save(item); return "redirect:/todo"; } }
TodoItem.java
package nyata.domain.service; import java.util.List; import nyata.domain.model.TodoItem; /** * Todoアイテムのフォーム * @author nyata */ public class TodoItemForm { /* 表示リスト(完了・未完)の切替フラグ */ private boolean isDone; /* 表示するTodoリスト */ private List<TodoItem> todoItems; // setter, getter public boolean isDone() { return isDone; } public void setDone(boolean isDone) { this.isDone = isDone; } public List<TodoItem> getTodoItems() { return todoItems; } public void setTodoItems(List<TodoItem> todoItems) { this.todoItems = todoItems; } }
TodoItemRepository.java
JPAを使う用のリポジトリ。ソートや一括更新のため少しメソッド追加。
ユーザーごとのアイテムリストを表示する。
package nyata.domain.repositoriy; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import nyata.domain.model.TodoItem; import nyata.domain.model.User; /** * Todoアイテムのリポジトリ * @author nyata */ public interface TodoItemRepository extends JpaRepository<TodoItem, Long> { public List<TodoItem> findByDoneAndUser(boolean done, User user); public List<TodoItem> findByDoneAndUserOrderByTododateAsc(boolean done, User user); public long deleteByDoneAndUser(boolean done, User user); }
TodoItemForm.java
画面に表示内容を受け渡す役割
package nyata.domain.service; import java.util.List; import nyata.domain.model.TodoItem; /** * Todoアイテムのフォーム * @author nyata */ public class TodoItemForm { /* 表示リスト(完了・未完)の切替フラグ */ private boolean isDone; /* 表示するTodoリスト */ private List<TodoItem> todoItems; // setter, getter public boolean isDone() { return isDone; } public void setDone(boolean isDone) { this.isDone = isDone; } public List<TodoItem> getTodoItems() { return todoItems; } public void setTodoItems(List<TodoItem> todoItems) { this.todoItems = todoItems; } }
完成
ログイン機能よりすごく簡単に実装できた。フレームワークって簡単にアプリ作れてすごいなと思いました。
もう一つくらい、Springでアプリ作りたいと思ってる。ひとまずUIがしょぼいままなのでフロントの勉強として作成したアプリの見た目を整えていこうと思う。
GitHub - n-yata/ToDoSpring
はまったこと
画面遷移とリダイレクトでパスの書き方が異なる
単純に画面遷移するときは、return "todo";
リダイレクトのときは、 return "redirect:/todo";
頭に"/"をつけたりつけなかったり。ちゃんと区別しないと怒られる。
一括更新の仕方
SQLだったらUPDATE todoitems SET done = 'false' WHERE user_id = 'hogehoge'とかでまとまりのまま一括更新できるのだけど、 JPAで一括UPDATEするときにfor文利用する方法しか見つからなかった。
todoItemForm.setTodoItems(this.repository.findByDoneAndUser(false, userDetails.getUser())); for (TodoItem todoitem : todoItemForm.getTodoItems()) { todoitem.setDone(true); this.repository.save(todoitem); }
なんかSQLの良さを殺してしまうような書き方な気がして気持ち悪いので気が向いたら修正する。
JPAの書き方
日本語の記事少ない。英語ちゃんと勉強しようと思いました・・・
参考文献
Spring BootでToDoアプリを作ってみた - かずきのBlog@hatena
[Spring MVC] パス変数の受け渡し方について - Qiita
Spring Data JPA で遊んでみる 〜その6〜 - Yamkazu's Blog
令和時代に「Spring入門」「Spring徹底入門」を読むとき気をつけるべきN個のこと - Qiita
Spring BootでTodoアプリ1 -ログイン機能作成-
前置き
以前Spring徹底入門を読んでから、業務で触れることなかったのでいったんSpring勉強してなかった。
週末にまとまった勉強時間が取れるようになって、やりたい勉強できるようになったのでSpring Bootでアプリ作ってみた。
相も変わらず作成したアプリはHerokuにアップ。
Spring徹底入門はリファレンスとしてアプリ作りながら読むとすごく勉強になる。
アプリの内容
Todoアプリ。ログイン機能とTodoを作る。
DBはHerokuで使用できるPostgreSQL。
本記事ではログイン機能を作成。
テーブル作成
Todoアプリ用DBを作って、ユーザー管理テーブルを作成。
CREATE DATABASE tododb; DROP TABLE IF EXISTS usr CASCADE; CREATE TABLE IF NOT EXISTS usr( user_id VARCHAR(255) NOT NULL, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, role_name VARCHAR(255) NOT NULL, PRIMARY KEY (user_id) );
インサートするデータはこんな感じ。
INSERT INTO usr (user_id, first_name, last_name, role_name, password) VALUES ('user', '太郎', '山田', 'USER', '$2a$10$ZqU0cizLk7c6YEydWPfpr.3Gmomn5JtYOfBqEExy3muSNKm3sZY7a');
passwordはBCryptでハッシュ値に変換したものを入れとく。
上記のパスワードは「password」をハッシュ化したもの。
ハッシュ化は下記のサイトで計算できる。(ありがたい)
BCrypt ハッシュ値 計算 | tekboy
ストレッチング回数は10回に指定してハッシュ化。
プロジェクト作成
eclipseで開発。最近のpleiadesならSTS標準で入ってるはず。
Spring Boot 開発環境構築 Eclipse STS インストール - 公式ガイド
プロジェクトを作成。
pom.xmlのdependenciesタグ内にSpring Securityを追加。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency>
application.propertiesにPostgreSQLの接続情報を追加。
ついでにログ出力も設定。
# DB接続設定 spring.datasource.url=jdbc:postgresql://localhost:5432/tododb spring.datasource.username=postgres spring.datasource.password=postgres spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.hibernate.ddl-auto=none spring.jpa.show-sql=true # ログ出力の設定 logging.file.name=C:/logs/todoSpring.log #logging.level.org.springframework.web=DEBUG logging.level.org.springframework.web=INFO
HTMLの作成
ログイン画面のHTMLを作成。
「/SpringTodo/src/main/resources/templates」内にlogin.htmlを作成。
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <meta charset="UTF-8"> <title>Login Form</title> </head> <body> <div th:if="${param.error}"> Invalid username and password. </div> <div th:if="${param.logout}"> You have been logged out. </div> <form th:action="@{/login}" method="post"> <div><label> User Name : <input type="text" name="username" /></label></div> <div><label> Password : <input type="password" name="password" /></label></div> <div><input type="submit" value="Sign In"></div> </form> </body> </html>
ログインが成功したときの遷移先を作成。
todo.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>Hello World!</title> </head> <body> <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1> <form th:action="@{/logout}" method="post"> <input type="submit" value="Sign Out"/> </form> </body> </html>
Javaを書く
作成するソースは下記。
/SpringTodo/src/main/java/nyata/
- MvcConfig.java
- SpringTodoApplication.java(今回は編集しない)
- WebSecurityConfig.java
/SpringTodo/src/main/java/nyata/domain/model
- User.java
- RoleName.java
/SpringTodo/src/main/java/nyata/domain/repositoriy
- UserRepository.java
/SpringTodo/src/main/java/nyata/domain/service
- TodoUserDetails.java
- TodoUserDetailsService.java
MvcConfig.java
URLの遷移先を管理
package nyata; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * View Controller * @author nyata */ @Configuration public class MvcConfig implements WebMvcConfigurer { public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/login").setViewName("login"); registry.addViewController("/").setViewName("login"); registry.addViewController("/todo").setViewName("todo"); } }
WebSecurityConfig.java
Spring Securityの設定情報。
パスワードのハッシュ化としてBCryptを使用。
package nyata; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import nyata.domain.service.TodoUserDetailsService; /** * Security Config * @author nyata */ @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired TodoUserDetailsService userDetailsService; @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/js/**", "/css/**").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .usernameParameter("username") .passwordParameter("password") .defaultSuccessUrl("/todo", true) .permitAll() .and() .logout() .permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } }
RoleName.java
package nyata.domain.model; /** * ユーザー権限一覧 * @author nyata */ public enum RoleName { ADMIN, USER }
User.java
package nyata.domain.model; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.Id; import javax.persistence.Table; import org.hibernate.annotations.Proxy; /** * ユーザー情報のエンティティ * @author nyata */ @Entity @Proxy(lazy = false) @Table(name = "usr") public class User { /* ユーザーID */ @Id private String userId; /* パスワード */ private String password; /* ファーストネーム */ private String firstName; /* ラストネーム */ private String lastName; /* ユーザー権限 */ @Enumerated(EnumType.STRING) private RoleName roleName; // setter, getter public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public RoleName getRoleName() { return roleName; } public void setRoleName(RoleName roleName) { this.roleName = roleName; } }
UserRepository.java
package nyata.domain.repositoriy; import org.springframework.data.jpa.repository.JpaRepository; import nyata.domain.model.User; /** * ユーザー情報のリポジトリ * @author nyata */ public interface UserRepository extends JpaRepository<User, String> { }
TodoUserDetails.java
ユーザー認証の設定
package nyata.domain.service; import java.util.Collection; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetails; import nyata.domain.model.User; /** * ユーザー認証情報 * @author nyata */ public class TodoUserDetails implements UserDetails { private final User user; public TodoUserDetails(User user) { this.user = user; } public User getUser() { return user; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return AuthorityUtils.createAuthorityList("ROLE_" + this.user.getRoleName().name()); } @Override public String getPassword() { return this.user.getPassword(); } @Override public String getUsername() { return this.user.getUserId(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
TodoUserDetailsService.java
ユーザー認証処理を受け持つ
package nyata.domain.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import nyata.domain.model.User; import nyata.domain.repositoriy.UserRepository; /** * ユーザー認証のサービス * @author nyata */ @Service public class TodoUserDetailsService implements UserDetailsService { @Autowired UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.getOne(username); if (user == null) { throw new UsernameNotFoundException(username + " is not found."); } return new TodoUserDetails(user); } }
ひとまずこれでログイン機能完成。次はTodo機能を作っていく。
参考文献
Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発(株式会社NTTデータ)|翔泳社の本
Spring Boot ログイン画面 - 公式サンプルコード
BCrypt ハッシュ値 計算 | tekboy
Spring Bootでログを出力する | DevelopersIO
SpringSecurity-AuthenticatedPricipalは非推奨 - Javaer101
WindowsでJIS配列からUS配列キーボードに変更したときに設定したこと
前置き
US配列キーボードには前からなんだか憧れがあった。「できるプログラマならUS配列!」みたいな雰囲気をググってるとよく見てて、それに影響されてUSキーボードを買ってみた。 確かにホームポジションからEnterキーまで距離少し近いとか、コード書いてると、プログラミング言語の構文ってUS配列用にできてるなって感じる。 いったんどうしても解決できない課題があったのでJIS配列に戻したけど、またいずれUS配列に切り替えるようのメモとして残す。
USキー配列への設定変更
ググれば結構出てくるけどおさらい。
下記の画像の通りに進んで再起動すればUS配列用に設定できる
ノートPCのキーボード(JIS配列)を無効化
別に必須じゃないけど。ノートPCのキーボードの上に物理的に外付けキーボード置いて使ったりするので無効にした。下記の記事を参考にした。
qiita.com
VSCodeの挙動用に設定変更
困ったことにVSCodeで「Ctl+`(バッククォート)」が反応しない。
(統合ターミナルを表示するためのショートカット)
下記の記事を参考にWindowsの言語に英語を追加した。
qiita.com
在宅勤務でホストPCにリモート接続は難あり
ホストPCの設定がJIS配列のままだからか、リモートデスクトップ製品の問題か、自宅PC(US配列)からキーボード入力すると、配列の違いから入力ができない文字がある。 残念ながらSESという業態で勤務している以上、ホストPCは勝手に設定できないし、US配列で作業は支障が出るのでいったんJIS配列に戻した。
ラズパイ(Debian 10)にサーバー(Tomcat 9)入れて公開する手前まで
- 前置き
- 買ったやつ
- まずは最新状態にしてバージョン確認
- Tomcatをインストールする
- PostgreSQLのインストール
- warファイルをデプロイしてみる
- 公開するためにセキュリティ面を整える
- そしていざ公開!
前置き
いずれ自分でサーバー立てたいと思ってたけど、直近ではそんなつもりなかった。 自社の人と会話する機会があって、ラズパイの魅力をたくさん教えてもらって衝動買い。 せっかくなので勉強がてら自分のサーバーを立ててみることにした。 (最後に記載するけど、物理的な障害がありサーバー公開するには至っていない)
買ったやつ
ラズパイと周辺機器全部入り。OSもraspbianがSDカードにインストール済み。 チキンなので全部そろってすぐ起動できるやつ買った。初期設定関係はこの記事では省略。
まずは最新状態にしてバージョン確認
ラズパイにインストールされてるもろもろとかを最新状態にする。
セキュリティ的にも定期的に最新状態にする必要あるのだろうなあ(面倒)
# 最新のパッケージをインストール $ sudo apt update $ sudo apt upgrade # OSのアップデート $ sudo apt-get dist-upgrade # ラズパイのファームウェアのアップデート $ sudo rpi-update # debianのバージョン確認 $ cat /etc/debian_version
raspbianとdebianの違いはよくわかってない。けどサーバーとして使う分には一緒っぽい。 debianのバージョンわかったら「debian 10 tomcat インストール」とかでググる。
Tomcatをインストールする
自分の場合はバージョン10だったので、下記のような記事がヒットした。
記事を参考にしながら(というか記事そのまま)必要なツールをインストールしていく
Debian 10にApache Tomcat 9をインストールする方法
Tomcatインストールして設定完了したら、ブラウザに「localhost:8080」とかでTomcatのページ表示されるか確認。 他のPCから同一ネットワーク内のラズパイにアクセスするなら、「ラズパイのIPアドレス:8080」で表示できる。 ラズパイIPアドレスは下記コマンドで調べる。wifiの場合はwlan0のinetってとこ(192.168..とか)。
$ ifconfig
メモとしてlinuxでのusergroupのコマンド
(ユーザーの追加は-aをつけ忘れると追加ではなく置き換えになる。全部グループ消えるので注意!)
# ユーザーのgroupを確認 $ groups username # ユーザーの追加 $ sudo usermod -aG groups username
PostgreSQLのインストール
DBは使い慣れているPostgreSQLをインストール。
環境構築:Debian 10(buster) にPostgreSQL 11をインストールし、新規DBを作成する方法
上記記事を参考にPostgre接続用ユーザーを作成した場合、ユーザー名のDBは作成されていないので
$ psql -U 接続用ユーザー名
ってやったら失敗した。
$ psql -U 接続用ユーザー名 -d postgres
とかって作成済みのDB名を指定する必要がある。
(もしくは接続用ユーザー名と同じ名前のDBを作成する。よろしくないだろうけど)
warファイルをデプロイしてみる
アプリ作ってwarで固めたらTomcatにwarファイルを配置する。
tomcatユーザーでないと権限無いのでスイッチする必要ある。
# tomcatユーザーにスイッチ $ su - tomcat # warファイルを移動 $ mv appname.war /opt/tomcat/webapps/
少しすると勝手にwarを展開してくれてサービス開始する。 下記はTomcatコントロールする用のコマンド
# 起動 $ sudo systemctl start tomcat # 停止 $ sudo systemctl stop tomcat # リスタート $ sudo systemctl restart tomcat # ステータス確認 $ sudo systemctl status tomcat
URLを入れて動いてるか確認。
localhost:8080/appname
公開するためにセキュリティ面を整える
下記記事を参考にした。
raspberry piでお手軽自家サーバー webサーバー編 - Qiita
そしていざ公開!
(まだセキュリティダメかもしれないけど)とにかく公開したくて自宅のルーターいじってポート開放!
しようとして、さきほどの記事見ながらなんちゃらやってたら、使ってるwifiルーターが公式でポート開放できないようにしてることが判明した。
SoftBank Air における機能制限のお知らせ | インターネット・固定電話 | ソフトバンク
つよつよを目指すためには、物理的な障害も解決しないといけないことを学んだ(完)