CRUD操作を行うREST APIを作成する
とりあえずバリデーションはなしでCURD操作に注力する
build.gradle
plugins { id 'org.springframework.boot' version '2.5.0' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java' } group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' repositories { mavenCentral() } dependencies { compileOnly 'org.projectlombok:lombok' implementation 'org.slf4j:slf4j-api' implementation 'ch.qos.logback:logback-core' implementation 'ch.qos.logback:logback-classic' implementation 'com.google.guava:guava:30.1.1-jre' implementation 'org.apache.commons:commons-lang3' implementation 'commons-beanutils:commons-beanutils:1.9.2' implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.4' runtimeOnly 'mysql:mysql-connector-java' testImplementation 'org.springframework.boot:spring-boot-starter-test' } test { useJUnitPlatform() }
/src/main/resources/application.yml
server: servlet: context-path: /sample spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:33060/sample username: mysql-app password: mysql-app mybatis: configuration: map-underscore-to-camel-case: true logging: level: org: springframework: WARN com: example: demo: domain: mapper: DEBUG
/src/main/java/com/example/demo/DemoApplication.java
package com.example.demo; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; // https://spring.pleiades.io/spring-boot/docs/current/reference/html/features.html @SpringBootApplication @MapperScan("com.example.demo.domain.mapper") public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
/src/main/java/com/example/demo/web/controller/UsersController.java
package com.example.demo.web.controller; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import com.example.demo.service.UsersService; import com.example.demo.service.dto.UserDto; import com.example.demo.web.controller.helper.UsersHelper; import com.example.demo.web.dto.request.UsersCreateRequest; import com.example.demo.web.dto.request.UsersUpdateRequest; import com.example.demo.web.dto.response.UsersCreateResponse; import com.example.demo.web.dto.response.UsersGetListResponse; import com.example.demo.web.dto.response.UsersGetResponse; import lombok.RequiredArgsConstructor; @CrossOrigin @RestController @RequestMapping(value = "/users", produces = "application/json;charset=UTF-8") @RequiredArgsConstructor public class UsersController { private static final Logger LOGGER = LoggerFactory.getLogger(UsersController.class); private final UsersService usersService; @RequestMapping(method = RequestMethod.GET) public UsersGetListResponse getList() { List<UserDto> resultList = usersService.findAll(); return UsersHelper.convertToUsersGetListResponse(resultList); } @RequestMapping(method = RequestMethod.GET, value = "{id}") public UsersGetResponse get(@PathVariable("id") Long id) { LOGGER.info("#id : {}", id); UserDto result = usersService.findById(id); return UsersHelper.convertToUsersGetResponse(result); } @RequestMapping(method = RequestMethod.POST) @ResponseStatus(HttpStatus.CREATED) public UsersCreateResponse create(@Validated @RequestBody UsersCreateRequest request) { LOGGER.info("#UsersCreateRequest : {}", request); UserDto userDto = UsersHelper.convertFromUsersCreateRequest(request); UserDto result = usersService.create(userDto); return UsersHelper.convertToUsersCreateResponse(result); } @RequestMapping(method = RequestMethod.PUT, value = "{id}") @ResponseStatus(HttpStatus.NO_CONTENT) public void update(@PathVariable("id") Long id, @Validated @RequestBody UsersUpdateRequest request) { LOGGER.info("#id : {}", id); LOGGER.info("#UsersUpdateRequest : {}", request); UserDto userDto = UsersHelper.convertFromUsersUpdateRequest(id, request); usersService.update(userDto); } @RequestMapping(method = RequestMethod.DELETE, value = "{id}") @ResponseStatus(HttpStatus.NO_CONTENT) public void delete(@PathVariable("id") Long id) { LOGGER.info("#id : {}", id); usersService.delete(id); } }
/src/main/java/com/example/demo/web/controller/helper/UsersHelper.java
package com.example.demo.web.controller.helper; import java.util.List; import java.util.stream.Collectors; import com.example.demo.common.utils.ConvertUtils; import com.example.demo.service.dto.UserDto; import com.example.demo.web.dto.request.UsersCreateRequest; import com.example.demo.web.dto.request.UsersUpdateRequest; import com.example.demo.web.dto.response.UsersCreateResponse; import com.example.demo.web.dto.response.UsersGetListResponse; import com.example.demo.web.dto.response.UsersGetListResponse.User; import com.example.demo.web.dto.response.UsersGetResponse; public class UsersHelper { public static UsersGetListResponse convertToUsersGetListResponse(List<UserDto> list) { final UsersGetListResponse response = new UsersGetListResponse(); List<User> users = list.stream().map((dto) -> { final UsersGetListResponse.User user = new UsersGetListResponse.User(); ConvertUtils.copyProperties(dto, user); return user; }).collect(Collectors.toList()); response.setUsers(users); return response; } public static UsersGetResponse convertToUsersGetResponse(UserDto dto) { final UsersGetResponse response = new UsersGetResponse(); ConvertUtils.copyProperties(dto, response); return response; } public static UserDto convertFromUsersCreateRequest(UsersCreateRequest request) { final UserDto userDto = new UserDto(); ConvertUtils.copyProperties(request, userDto); return userDto; } public static UsersCreateResponse convertToUsersCreateResponse(UserDto dto) { final UsersCreateResponse response = new UsersCreateResponse(); ConvertUtils.copyProperties(dto, response); return response; } public static UserDto convertFromUsersUpdateRequest(Long id, UsersUpdateRequest request) { final UserDto userDto = new UserDto(); ConvertUtils.copyProperties(request, userDto); userDto.setUserId(id); return userDto; } }
/src/main/java/com/example/demo/web/dto/request/UsersCreateRequest.java
package com.example.demo.web.dto.request; import lombok.Data; @Data public class UsersCreateRequest { private String userName; private String mailAddress; private String password; private String confirmPassword; }
/src/main/java/com/example/demo/web/dto/request/UsersUpdateRequest.java
package com.example.demo.web.dto.request; import lombok.Data; @Data public class UsersUpdateRequest { private String userName; private String mailAddress; private String password; private String confirmPassword; private String deleted; }
/src/main/java/com/example/demo/web/dto/response/UsersGetListResponse.java
package com.example.demo.web.dto.response; import java.util.List; import com.fasterxml.jackson.annotation.JsonInclude; import com.google.common.collect.Lists; import lombok.Data; @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class UsersGetListResponse { private List<User> users = Lists.newArrayList(); @Data public static class User { private String userId; private String userName; private String mailAddress; } }
/src/main/java/com/example/demo/web/dto/response/UsersGetResponse.java
package com.example.demo.web.dto.response; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class UsersGetResponse { private String userId; private String userName; private String mailAddress; }
/src/main/java/com/example/demo/web/dto/response/UsersCreateResponse.java
package com.example.demo.web.dto.response; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class UsersCreateResponse { private String userId; private String userName; private String mailAddress; private String registTime; private String updateTime; }
/src/main/java/com/example/demo/service/UsersService.java
package com.example.demo.service; import java.util.List; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.demo.domain.model.Users; import com.example.demo.domain.repository.UsersRepository; import com.example.demo.service.dto.UserDto; import com.example.demo.utils.DateUtils; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor public class UsersService { private static final Logger LOGGER = LoggerFactory.getLogger(UsersService.class); private final UsersRepository usersRepository; @Transactional(readOnly = true) public List<UserDto> findAll() { return usersRepository.findAll().stream().map((users) -> { return convert(users); }).collect(Collectors.toList()); } @Transactional(readOnly = true) public UserDto findById(Long id) { Users users = usersRepository.findById(id); if (users == null) { return null; } return convert(users); } @Transactional(readOnly = false) public UserDto create(UserDto dto) { final Users users = Users.builder() .mailAddress(dto.getMailAddress()) .userName(dto.getUserName()) .password(dto.getPassword()) .registTime(DateUtils.getThreadDateTime()) .updateTime(DateUtils.getThreadDateTime()) .deleted(0) .build(); usersRepository.create(users); LOGGER.info("#users : {}", users); return convert(users); } @Transactional(readOnly = false) public void update(UserDto dto) { final Users users = Users.builder() .id(dto.getUserId()) .mailAddress(dto.getMailAddress()) .userName(dto.getUserName()) .password(dto.getPassword()) .updateTime(DateUtils.getThreadDateTime()) .deleted(dto.getDeleted()) .build(); usersRepository.update(users); } @Transactional(readOnly = false) public void delete(Long id) { usersRepository.delete(id); } private UserDto convert(Users users) { UserDto dto = new UserDto(); dto.setUserId(users.getId()); dto.setMailAddress(users.getMailAddress()); dto.setUserName(users.getUserName()); dto.setRegistTime(users.getRegistTime()); dto.setUpdateTime(users.getUpdateTime()); dto.setDeleted(users.getDeleted()); return dto; } }
/src/main/java/com/example/demo/service/dto/UserDto.java
package com.example.demo.service.dto; import java.util.Date; import lombok.Data; @Data public class UserDto { private Long userId; private String userName; private String mailAddress; private String password; private Date registTime; private Date updateTime; private Integer deleted; }
/src/main/java/com/example/demo/domain/repository/UsersRepository.java
package com.example.demo.domain.repository; import java.util.List; import org.springframework.stereotype.Repository; import com.example.demo.domain.mapper.UsersMapper; import com.example.demo.domain.model.Users; import lombok.RequiredArgsConstructor; @Repository @RequiredArgsConstructor public class UsersRepository { private final UsersMapper usersMapper; public List<Users> findAll() { return usersMapper.findAll(); } public Users findById(Long id) { return usersMapper.findById(id); } public int create(Users users) { return usersMapper.insert(users); } public int update(Users users) { return usersMapper.update(users); } public int delete(Long id) { return usersMapper.delete(id); } }
/src/main/java/com/example/demo/domain/mapper/UsersMapper.java
package com.example.demo.domain.mapper; import java.util.List; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import com.example.demo.domain.model.Users; @Mapper public interface UsersMapper { List<Users> findAll(); Users findById(@Param("id") Long id); Integer insert(@Param("users") Users users); Integer update(@Param("users") Users users); Integer delete(@Param("id") Long id); }
/src/main/resources/com/example/demo/domain/mapper/UsersMapper.xml
<?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="com.example.demo.domain.mapper.UsersMapper"> <select id="findAll" resultType="com.example.demo.domain.model.Users"> SELECT ID, MAIL_ADDRESS, USER_NAME, PASSWORD, LAST_LOGIN_TIME, REGIST_TIME, UPDATE_TIME, DELETED FROM t_users WHERE DELETED = 0 ORDER BY ID </select> <select id="findById" resultType="com.example.demo.domain.model.Users"> SELECT ID, MAIL_ADDRESS, USER_NAME, PASSWORD, LAST_LOGIN_TIME, REGIST_TIME, UPDATE_TIME, DELETED FROM t_users WHERE ID = #{id} </select> <insert id="insert" useGeneratedKeys="true" keyProperty="id"> INSERT INTO t_users ( MAIL_ADDRESS, USER_NAME, PASSWORD, LAST_LOGIN_TIME, REGIST_TIME, UPDATE_TIME, DELETED ) VALUES ( #{users.mailAddress}, #{users.userName}, SHA2(#{users.password}, 256), NULL, #{users.registTime}, #{users.updateTime}, 0 ) </insert> <update id="update"> UPDATE t_users SET UPDATE_TIME = #{users.updateTime} <if test="users.mailAddress != null"> ,MAIL_ADDRESS = #{users.mailAddress} </if> <if test="users.userName != null"> ,USER_NAME = #{users.userName} </if> <if test="users.password != null"> ,PASSWORD = SHA2(#{users.password}, 256) </if> <if test="users.deleted != null"> ,DELETED = #{users.deleted} </if> WHERE ID = #{users.id} </update> <delete id="delete"> DELETE FROM t_users WHERE ID = #{id} </delete> </mapper>
/src/main/java/com/example/demo/common/utils/DateUtils.java
package com.example.demo.common.utils; import java.util.Date; import java.util.regex.Pattern; import org.apache.commons.lang3.time.DateFormatUtils; public class DateUtils { private static final String THREAD_LOCAL_KEY = "___THREAD_LOCAL_DATE___"; // ISO8601拡張形式 private static final Pattern DEFAULT_DATE_TIME_FORMAT_PATTERN = Pattern .compile("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\+[0-9]{2}:[0-9]{2}"); private DateUtils() { } public static void setThreadDateTime(final Date datetime) { ThreadLocalUtils.set(THREAD_LOCAL_KEY, datetime); } public static Date getThreadDateTime() { return ThreadLocalUtils.get(THREAD_LOCAL_KEY, Date.class); } public static String format(Date date) { if (date == null) { return null; } return DateFormatUtils.ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.format(date); } public static Date parse(String strDate) { try { return DateFormatUtils.ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.parse(strDate); } catch (Exception e) { return null; } } public static boolean isDefaultDateTimeFormat(String str) { if (str == null) { return false; } return DEFAULT_DATE_TIME_FORMAT_PATTERN.matcher(str).matches(); } }
/src/main/java/com/example/demo/common/utils/ThreadLocalUtils.java
package com.example.demo.common.utils; import java.util.Map; import com.google.common.collect.Maps; public class ThreadLocalUtils { private ThreadLocalUtils() { } private static final ThreadLocal<Map<String, Object>> POOL = new ThreadLocal<Map<String, Object>>() { @Override protected Map<String, Object> initialValue() { return Maps.newHashMap(); } }; public static Object set(String key, Object value) { return POOL.get().put(key, value); } public static <T> T get(String key, Class<T> clazz) { return clazz.cast(POOL.get().get(key)); } public static void clear() { POOL.get().clear(); } }
/src/main/java/com/example/demo/common/utils/ConvertUtils.java
package com.example.demo.common.utils; import java.lang.reflect.InvocationTargetException; import java.util.Date; import org.apache.commons.beanutils.BeanUtilsBean; import org.apache.commons.beanutils.ConvertUtilsBean; import org.apache.commons.beanutils.Converter; import org.apache.commons.beanutils.converters.DateTimeConverter; public class ConvertUtils { private static final BeanUtilsBean BUB; static { BUB = new BeanUtilsBean(new ConvertUtilsBean(), BeanUtilsBean.getInstance().getPropertyUtils()); BUB.getConvertUtils().register(new MyStringConverter(), String.class); final MyDateConverter myDateConverter = new MyDateConverter(); myDateConverter.setPatterns(new String[] { "yyyyMMdd", "yyyyMMddHHmmss", "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss" }); BUB.getConvertUtils().register(myDateConverter, Date.class); } static class MyStringConverter implements Converter { @Override public <T> T convert(Class<T> type, Object value) { if (value == null) { return null; } else { if (value instanceof java.util.Date) { return type.cast(DateUtils.format((Date) value)); } return type.cast(value.toString()); } } } static class MyDateConverter extends DateTimeConverter { public MyDateConverter() { super(); } public MyDateConverter(Object defaultValue) { super(defaultValue); } @Override protected Class<?> getDefaultType() { return Date.class; } @Override public <T> T convert(Class<T> type, Object value) { if (value != null && value instanceof java.lang.String && DateUtils.isDefaultDateTimeFormat((String) value)) { return type.cast(DateUtils.parse((String) value)); } else { return super.convert(type, value); } } } public static void copyProperties(Object src, Object dest) { try { BUB.copyProperties(dest, src); } catch (IllegalAccessException | InvocationTargetException e) { throw new IllegalStateException(e); } } }
/src/main/java/com/example/demo/web/filter/ThreadLocalFilter.java
package com.example.demo.web.filter; import java.io.IOException; import java.util.Date; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.springframework.stereotype.Component; import com.example.demo.common.utils.DateUtils; @Component public class ThreadLocalFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { DateUtils.setThreadDateTime(new Date()); chain.doFilter(request, response); } }
Spring Bootの開発環境を作る
eclipseの準備
Eclipse マーケットプレースから下記のプラグインをインストール
- Spring Tool4(aka Spring Tool Suite 4)
- Gradle IDE Pack
プロジェクトの作成
Spring Starter Projectからプロジェクトを作成
設定
SpringMVCを使いたいのでbuild.gradleの依存関係を編集する
build.gradle
plugins { id 'org.springframework.boot' version '2.5.0' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java' } group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter' // ↓これを追加 implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' } test { useJUnitPlatform() }
ソース
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
package com.example.demo.controller; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @CrossOrigin @RestController @RequestMapping(value = "/sample", produces = "application/json;charset=UTF-8") public class SampleController { @RequestMapping(method = RequestMethod.GET, value = "/") public Map<String, Object> get() { Map<String, Object> map = new HashMap<>(); map.put("String", "あいうえお"); map.put("Num", 123); map.put("Date", new Date()); return map; } }
実行
Package Explorer からDemoApplication を選択して、Run As → Spring Boot App を選択して実行
MySQLの環境を作る
docker-composeでMySQLを利用できる環境を作る
※mysqlのイメージをそのまま利用するとWindowsでは下記の警告が発生し、my.cnfが読み込めなかった。この為、Dockerfile を介してパーミッションを変更する処理を追加した。
mysqld: [Warning] World-writable config file '/etc/mysql/conf.d/my.cnf' is ignored.
Windows + docker-compose + mysql で文字化けさせない方法 - Qiita
ディレクトリ構成
docker ├─docker-compose.yml └─mysql ├─Dockerfile ├─conf.d │ └─my.cnf ├─data ├─logs └─initdb.d └─init.sql
docker-compose.yml
version: '3.8' services: db: container_name: oursql5.7 build: ./mysql/ hostname: mysql environment: MYSQL_DATABASE: sample MYSQL_USER: mysql-app MYSQL_PASSWORD: mysql-app MYSQL_ROOT_PASSWORD: admin BIND-ADDRESS: 0.0.0.0 ports: - "33060:3306" volumes: - ./mysql/initdb.d:/docker-entrypoint-initdb.d - ./mysql/data:/var/lib/mysql - ./mysql/logs:/var/log/mysql tty: true
Dockerfile
FROM mysql:5.7 EXPOSE 3306 ADD ./conf.d/my.cnf /etc/mysql/conf.d/my.cnf RUN chmod 644 /etc/mysql/conf.d/my.cnf CMD ["mysqld"]
my.cnf
[mysqld] character-set-server=utf8mb4 #エラーログを出力 log-error=/var/log/mysql/error.log #詳細ログを出力 general_log=ON general_log_file=/var/log/mysql/query.log #Slow Queryログを出力 slow_query_log=ON # slow queryログの有効化 slow_query_log_file=/var/log/mysql/slow.log # ファイルパス long_query_time=5 # 5秒以上処理に時間がかかったクエリを記録 log-queries-not-using-indexes # インデックスが使用されていないクエリをログに出力 [mysql] default-character-set=utf8mb4 [mysqldump] default-character-set=utf8mb4
init.sql
CREATE TABLE IF NOT EXISTS t_users ( ID INT(11) NOT NULL auto_increment PRIMARY KEY COMMENT 'ID', MAIL_ADDRESS VARCHAR(256) NOT NULL COMMENT 'メールアドレス', USER_NAME VARCHAR(15) NOT NULL COMMENT 'ユーザ名', PASSWORD VARCHAR(256) NOT NULL COMMENT 'パスワード', LAST_LOGIN_TIME DATETIME COMMENT '最終ログイン日時', REGIST_TIME DATETIME NOT NULL COMMENT '登録日時', UPDATE_TIME DATETIME NOT NULL COMMENT '更新日時', DELETED TINYINT(1) NOT NULL DEFAULT 0 COMMENT '削除フラグ' ) DEFAULT CHARSET=utf8mb4 ; INSERT INTO t_users (MAIL_ADDRESS, USER_NAME, PASSWORD, LAST_LOGIN_TIME, REGIST_TIME, UPDATE_TIME) VALUES ('jack@hoge.example.jp', 'Jack', SHA2('1234567', 256), NULL, NOW(), NOW()), ('lary@hoge.example.jp', 'Lary', SHA2('1234567', 256), NULL, NOW(), NOW()), ('pitt@hoge.example.jp', 'Pitt', SHA2('1234567', 256), NULL, NOW(), NOW()) ;
起動
docker-compose up -d
疎通確認
mysql -u mysql-app -p -P 33060 sample Enter password: ********* Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 3 Server version: 5.7.34 MySQL Community Server (GPL) Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> select * from t_users; +----+----------------------+-----------+------------------------------------------------------------------+-----------------+---------------------+---------------------+--------------+ | ID | MAIL_ADDRESS | USER_NAME | PASSWORD | LAST_LOGIN_TIME | REGIST_TIME | UPDATE_TIME | DELETED| +----+----------------------+-----------+------------------------------------------------------------------+-----------------+---------------------+---------------------+--------------+ | 1 | jack@hoge.example.jp | Jack | 8bb0cf6eb9b17d0f7d22b456f121257dc1254e1f01665370476383ea776df414 | NULL | 2021-06-10 07:18:41 | 2021-06-10 07:18:41 | 0 | | 2 | lary@hoge.example.jp | Lary | 8bb0cf6eb9b17d0f7d22b456f121257dc1254e1f01665370476383ea776df414 | NULL | 2021-06-10 07:18:41 | 2021-06-10 07:18:41 | 0 | | 3 | pitt@hoge.example.jp | Pitt | 8bb0cf6eb9b17d0f7d22b456f121257dc1254e1f01665370476383ea776df414 | NULL | 2021-06-10 07:18:41 | 2021-06-10 07:18:41 | 0 | +----+----------------------+-----------+------------------------------------------------------------------+-----------------+---------------------+---------------------+--------------+ 3 rows in set (0.00 sec)
Optionalについて
nullの可能性のあるオブジェクトのラッパー。NullPointerExceptionを発生させない為に導入された仕組み。
Optionalオブジェクトの生成
public static Optional of(T value)
public static <T> Optional<T> of(T value) 指定された非null値を含むOptionalを返します。 型パラメータ: T - 値のクラス パラメータ: value - 存在する値、非nullである必要がある 戻り値: 存在する値でのOptional 例外: NullPointerException - valueがnullの場合。
public static Optional ofNullable(T value)
指定された値がnullでない場合はその値を記述するOptionalを返し、それ以外の場合は空のOptionalを返します。 型パラメータ: T - 値のクラス パラメータ: value - 記述する値(nullも可) 戻り値: 指定された値がnullでない場合は存在する値でのOptional、それ以外の場合は空のOptional
Optional<String> optional = Optional.ofNullable(new Random().nextBoolean() ? "hello" : null);
ofを使うケースなんてそもそもOptionalを使う必要がないのでは、、、
値の取り出し方
public T get()
このOptionalに値が存在する場合は値を返し、それ以外の場合はNoSuchElementExceptionをスローします。 戻り値: このOptionalが保持する非null値 例外: NoSuchElementException - 存在する値がない場合 関連項目: isPresent()
public boolean isPresent()
存在する値がある場合はtrueを返し、それ以外の場合はfalseを返します。 戻り値: 存在する値がない場合はtrue、それ以外の場合はfalse
public T orElse(T other)
存在する場合は値を返し、それ以外の場合はotherを返します。 パラメータ: other - 存在する値がない場合に返される値、nullも可 戻り値: 値(存在する場合)、それ以外の場合はother
Optional<String> optional = Optional.ofNullable(new Random().nextBoolean() ? "hello" : null); String value1 = optional.isPresent() ? optional.get() : "nullかよ"; String value2 = optional.orElse("nullかよ");
関数型インタフェースとあわせて使う
public T orElseGet(Supplier<? extends T> other)
値が存在する場合はその値を返し、そうでない場合はotherを呼び出し、その呼び出しの結果を返します。 パラメータ: other - Supplier(値が存在しない場合は、これの結果が返される) 戻り値: 値(存在する場合)、それ以外の場合はother.get()の結果 例外: NullPointerException - 値が存在せずotherがnullの場合
public void ifPresent(Consumer<? super T> consumer)
値が存在する場合は指定されたコンシューマをその値で呼び出し、それ以外の場合は何も行いません。 パラメータ: consumer - 値が存在する場合に実行されるブロック 例外: NullPointerException - 値が存在しconsumerがnullの場合
public Optional map(Function<? super T,? extends U> mapper)
値が存在する場合は、指定されたマッピング関数をその値に適用し、結果がnullでなければ結果を記述するOptionalを返します。それ以外の場合は空のOptionalを返します。 ここで、findFirstはOptional<String>を返してから、mapは求めているファイル(存在する場合)のOptional<FileInputStream>を返します。 型パラメータ: U - マッピング関数の結果の型 パラメータ: mapper - 値に適用するマッピング関数(存在する場合) 戻り値: 値が存在する場合はマッピング関数をこのOptionalの値に適用した結果を記述するOptional、それ以外の場合は空のOptional 例外: NullPointerException - マッピング関数がnullの場合
public Optional filter(Predicate<? super T> predicate)
値が存在し、それが指定された述語に一致する場合はその値を記述するOptionalを返し、そうでない場合は空のOptionalを返します。 パラメータ: predicate - 存在する場合は値に適用する述語 戻り値: 値が存在して値が指定された述語にマッチする場合は、このOptionalの値を記述するOptional、それ以外の場合は空のOptional 例外: NullPointerException - 述語がnullの場合
Optional<String> optional = Optional.ofNullable(new Random().nextBoolean() ? "hello" : null); String value3 = optional.orElseGet(() -> "nullかよ");
String str = new Random().nextBoolean() ? "hello" : null; if (str != null) { String result = "<" + str + ">"; System.out.println(result); }
↓
Optional<String> optional = Optional.ofNullable(new Random().nextBoolean() ? "hello" : null); optional.ifPresent((str) -> { String result = "<" + str + ">"; System.out.println(result); });
String str = new Random().nextBoolean() ? "hello" : null; List<String> list = null; if (str != null) { list = new ArrayList<String>(); list.add(str); } if (list != null) { System.out.println(list); }
↓
Optional<String> optional = Optional.ofNullable(new Random().nextBoolean() ? "hello" : null); Optional<List<String>> list = optional.map((str) -> { return List.of(str); }); list.ifPresent(System.out::println);
Streamの終端処理
void forEach(Consumer<? super T> action)
要素を順に処理する ※forEachメソッドは並列ストリームでは順序を保証しない為、順序を保証したいならforEachOrderedメソッドを使うこと
Stream.of("beckham", "zidane", "ronald").forEach(v -> System.out.println(v));
Optional findFirst()
最初の要素をOptionalで記述する ※並列ストリームで何でもよいので1件だけ取得するのであればfindAnyメソッドを使った方がパフォーマンスが良い
Optional<String> optional = Stream.of("beckham", "zidane", "ronald").findFirst(); System.out.println(optional.get());
boolean allMatch(Predicate<? super T> predicate)
boolean anyMatch(Predicate<? super T> predicate)
boolean noneMatch(Predicate<? super T> predicate)
それぞれ条件を満たす要素の存在をチェックする
System.out.println(IntStream.of(1, 3, 2).allMatch(v -> v >= 2)); // false ※全ての要素が条件を満たすか? System.out.println(IntStream.of(1, 3, 2).anyMatch(v -> v >= 2)); // true ※いずれかの要素が条件を満たすか? System.out.println(IntStream.of(1, 3, 2).noneMatch(v -> v >= 4)); // true ※条件を満たす要素が存在しないか?
Object toArray()
A toArray(IntFunction<A[]> generator)
要素の配列を返却する
int[] array = IntStream.of(1, 3, 2).toArray(); Person[] men = people.stream().toArray(Person[]::new);
R collect(Supplier supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)
このストリームの要素に対して可変リダクション操作を実行する。 Collectionの入れ替えなんかで利用する。
List<String> list = Stream.of("beckham", "zidane", "ronald").collect(Collectors.toList()); Set<String> set = Stream.of("beckham", "zidane", "ronald").collect(Collectors.toSet()); Map<String, String> map = Stream.of("beckham", "zidane", "ronald").collect(Collectors.toMap(String::toString, String::toString));
Optional min(Comparator<? super T> comparator)
Optional max(Comparator<? super T> comparator)
指定されたComparatorによる順序に従って、最小/最大の要素を取得する
Optional<Integer> min = List.of(3, 8, 7, 10, 5, 1, 9, 2, 4, 6).stream().min(Comparator.naturalOrder()); Optional<Integer> max = List.of(3, 8, 7, 10, 5, 1, 9, 2, 4, 6).stream().max(Comparator.naturalOrder());
long count()
要素の個数を取得する
long count = List.of(3, 8, 7, 10, 5, 1, 9, 2, 4, 6).stream().count();
int sum()
OptionalDouble average()
合計/平均を求める
int sum = IntStream.of(3, 8, 7, 10, 5, 1, 9, 2, 4, 6).sum(); OptionalDouble average = IntStream.of(3, 8, 7, 10, 5, 1, 9, 2, 4, 6).average();
Optional reduce(BinaryOperator accumulator)
T reduce(T identity, BinaryOperator accumulator)
U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator combiner)
リダクションを実行する
Optional<String> optional = Stream.of("beckham", "zidane", "ronald").reduce((result, str) -> result + "/" + str); // 初期値あり、非nullなので戻り値はOptionalではない String reduce = Stream.of("beckham", "zidane", "ronald").reduce("figo", (result, str) -> result + "/" + str); // Streamの型と最終結果の型が異なる場合に使用する Integer reduce = Stream.of("beckham", "zidane", "ronald").parallel().reduce( 0, // 初期値 // 個々の要素を処理してまとめる (result, value) -> { System.out.println("##################"); System.out.println(result); System.out.println(value); return result += value.length(); }, // 並列処理の結果をまとめる (result1, result2) -> { System.out.println(result1); System.out.println(result2); return result1 + result2; });
R collect(Supplier supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)
<R,A> R collect(Collector<? super T,A,R> collector)
要素をコレクションにまとめる
List<String> result = Stream.of("beckham", "zidane", "ronald") .collect( ArrayList<String>::new, (list, str) -> list.add(str), (list1, list2) -> list1.addAll(list2));
List<String> result = Stream.of("beckham", "zidane", "ronald") .collect( Collector.of( ArrayList<String>::new, ArrayList<String>::add, (list1, list2) -> { list1.addAll(list2); return list1; }, Collector.Characteristics.IDENTITY_FINISH ) );
String collect = Stream.of("beckham", "zidane", "ronald").collect(Collectors.joining(",", "<", ">")); System.out.println(collect); // <beckham,zidane,ronald>
関数型インタフェースとStream API
関数型インタフェースとStream APIの復習をしたのでメモ
関数型インタフェース
インタフェース | メソッド | 説明 |
---|---|---|
Function<T, R> | R apply(T t) | T型の引数を受け取ってR型の結果を戻す |
Consumer< T > | void accept(T t) | T型の引数を受け取って処理する(戻り値なし) |
Supplier | T get() | 引数に受け取らずに結果を戻す |
Predicate< T > | boolean test(T t) | 受け取った引数を評価/判定する |
// Function Function<Integer, Integer> func1 = (Integer i) -> i * 10; System.out.println(func1.apply(10)); Function<Integer, String> func2 = (Integer i) -> String.valueOf(i); System.out.println(func2.apply(10)); // Consumer Consumer<String> consumer1 = (String str) -> System.out.println(str); consumer1.accept("hello"); // Supplier Supplier<Date> supplier1 = () -> new Date(); System.out.println(supplier1.get()); // Predicate Predicate<Integer> predicate1 = (Integer i) -> i % 2 == 0; System.out.println(predicate1.test(2));
StreamAPI
コレクションに対する処理をラムダ式でかっちょよく操作するためのAPI。 上記の関数型インターフェースを引数にとる便利なメソッドが用意されている。
- Stream filter(Predicate
) - Stream map(Function<T, R>)
- Stream sorted(Comparator
) - void forEach(Consumer
)
List.of(3, 8, 7, 10, 5, 1, 9, 2, 4, 6) .stream() .filter((i) -> i % 2 != 0) .sorted((i, j) -> i - j) .map(i -> "<" + i + ">") .forEach(System.out::println); // <1><3><5><7><9> が出力
メソッド参照
上記のようにメソッドの引数でメソッドを参照させるための仕組み
呼び出したいメソッド名の直前に「::」を指定し、さらにその前にはクラス名を記述する。
メソッドの引数部分の「()」は省略する。
構文 | ラムダ式例 | 例 |
---|---|---|
クラスメソッド | クラス名::メソッド名 | String::valueOf |
インスタンスメソッド | オブジェクト名::メソッド名 | System.out::println |
コンストラクタ | クラス名::new | ArrayList::new |
vue-i18nにさわる
ロケールによってメッセージや項目名などの言語を切り替える仕組みですが、今回は単にVueテンプレートに埋め込む文字列を別ファイルに切り出したかったので使ってみます。
インストール
yarn add vue-i18n@next
設定
src/main.ts
import { createApp } from "vue"; import App from "./App.vue"; import router from "./router"; import stores from "./store"; import ElementPlus from "element-plus"; import "element-plus/lib/theme-chalk/index.css"; import { createI18n } from "vue-i18n"; const i18n = createI18n({ locale: "ja", messages: { ja: (await import("@/locales/ja.json")).default, }, }); const app = createApp(App); app.use(router); app.use(ElementPlus); stores.forEach(({ store, key }) => { app.use(store, key); }); app.use(i18n); app.mount("#app");
src/locales/ja.json
{ "label": { "homePage": { "message": "こんにちわわ!" } } }
src/views/menu/homePage.vue
<template lang="pug"> MenuLayout p {{$t("label.homePage.message")}} </template> <script lang="ts"> import { defineComponent } from "vue"; import MenuLayout from "@/layouts/MenuLayout.vue"; export default defineComponent({ name: "HomePage", components: { MenuLayout, }, }); </script>
参照する際は$t関数にJSONのパスを指定して参照する
$t("label.homePage.message")