[Spring Boot][java] app開発2

src/main/resources/schema.sql

DROP TABLE IF EXISTS meeting_room CASCADE;
DROP TABLE IF EXISTS reservable_room CASCADE;
DROP TABLE IF EXISTS reservation CASCADE;
DROP TABLE IF EXISTS usr CASCADE;

CREATE TABLE IF NOT EXIST meeting_room(
	room_id SERIAL NOT NULL,
	room_name VARCHAR(255) NOT NULL,
	PRIMARY KEY (room_id)	
);


CREATE TABLE IF NOT EXIST reservable_room(
	reserved_data DATE NOT NULL,
	room_id INT4 NOT NULL,
	PRIMARY KEY(reserved_date, room_id)
);

CREATE TABLE IF NOT EXIST meeting_room(
	reservation_id SERIAL NOT NULL,
	end_tim TIME NOT NULL,
	start_time TIME NOT NULL,
	reserved_date DATE NOT NULL,
	room_id INT4 NOT NULL,
	user_id VARCHAR(255) NOT NULL,
	PRIMARY KEY(reservation_id)
);

CREATE TABLE IF NOT EXIST meeting_room(
	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)
);

repository class

package mrs.domain.repository.room;

import java.time.LocalDate;
import java.util.List;

import mrs.domain.model.ReservableRoom;
import mrs.domain.model.ReservableRoomId;

import org.springframework.data.jpa.repository.JpaRepository;

public interface ReservableRoomRepository extends JpaRepository<ReservableRoom, ReservableRoomId>{
	List<ReservableRoom> findByReservableRoom_reservedDateOrderByReservableRoomId_roomIdAsc(LocalDate reservedDate);
}

-> importで先ほど作ったmodelを読み込んでますね。なるほど、少しわかってきた。

domain/service/RoomService.java

package mrs.domain.repository.room;

import java.time.LocalDate;
import java.util.List;

import mrs.domain.model.ReservableRoom;
import mrs.domain.repository.room.ReservableRoomRepository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class RoomService {
	
	@Autowired
	ReservableRoomRepository reservableRoomRepository;
	
	public List<ReservableRoom> findReservableRooms(LocalDate date){
		return reservableRoomRepository.findByReservableRoomId_reservedDateOrderByReservableRoomId_roomIdAsc(date);
	}
}

app/room/RoomsController.java

package mrs.app.room;

import java.time.LocalDate;
import java.util.List;

import mrs.domain.model.ReservableRoom;
import mrs.domain.service.room.RoomService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("rooms")
public class RoomsController {
	@Autowired
	RoomService roomService;
	
	@RequestMapping(method = RequestMethod.GET)
	String listRooms(Model model)
		LocalDate today = LocalDate.now();
		List<ReservableRoom> rooms = roomService.findReservableRooms(today);
		model.addAttribute("date", today);
		model.addAttribute("rooms", rooms);
		return "room/listRooms";
}

-> ん? ControllerのRequestMappingでクエリ検索してる?

/main/resources/templates/room/listRooms.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title th:text="|${#temporals.format(date, 'yyyy/M/d')}の会議室|">2021/01/29の会議室</title>
</head>
<body>
<h3>会議室</h3>
<a th:href="@{'/rooms/' + ${date.minusDays(1)}}">&lt; 前日</a>
<span th:text="|${#temporals.format(date, 'yyyy/M/d')}の会議室|">2021/01/29の会議室</span>
<a th:href="@{'/rooms/' + ${date.plusDays(1)}}">翌日 &gt;</a>

<ul>
	<li th:each="room: ${rooms}">
		<a th:href="@{'/reservations/' + ${date}+ '/' + ${room.meetingRoom.roomId}}"
		 th:text="${room.meetingRoom.roomName}"></a>
	</li>
</ul>
</body>
</html>

-> thymeleafはthで変数入れてますね。th:eachはforeachっぽい。

RoomsController

	String listRooms(@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) @PathVariable("date") LocalDate date, Model model)

規約が多い印象だが、想像してたより複雑ではなさそう。。

[Spring Boot][java] app開発1

STS4 -> File -> Spring Starter Project


SQL: JPA, PostgreSQL
Template Engines: Thymeleaf
Web: Web

ドメイン層: Model, Repository, Service
アプリケーション層: Controller, View(HTML)

# postgresでデータベース作成
$ createdb test -O root

Spring Bootの設定はapplication.propertiesに集約されている
application.properties

spring.jpa.database=POSTGRESQL
spring.datasource.url=jdbc:postgresql://localhost:5432/test
spring.datasource.username=root
spring.datasource.password=hoge
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.format_sql=true
spring.datasource.sql-script-encoding=UTF-8
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

# ライブラリ追加

		<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-java8time</artifactId>
			<scope>2.1.0.RELEASE</scope>
		</dependency>

モデルの配下にエンティティを作成していく
User.java

package mrs.domain.model;

import java.io.Serializable;

import javax.persistence.*;

@Entity
@Table(name="usr")
public class User implements Serializable {
	@Id
	private String userId;
	private String password;
	private String firstName;
	private String lastName;
	
	@Enumerated(EnumType.STRING)
	private RoleName roleName;
}

RoleName.java

package mrs.domain.model;

public enum RoleName {
	ADMIN, USER
}

MeetingRoom.java

package mrs.domain.model;

import java.io.Serializable;

import javax.persistence.*;

@Entity
public class MeetingRoom implements Serializable {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer roomId;
	
	private String roomName;
}

ReservableRoom.java

package mrs.domain.model;

import java.io.Serializable;

import javax.persistence.*;

@Entity
public class ReservableRoom implements Serializable {
	@EmbeddedId
	private ReservableRoomId reservableRoomId;
	
	@ManyToOne
	@JoinColumn(name="room_id", insertable=false, updatable= false)
	@MapsId("roomId")
	private MeetingRoom meetingRoom;
	
	public ReservableRoom(ReservableRoomId reservableRoomId) {
		this.reservableRoomId = reservableRoomId;
	}
	public ReservableRoom() {
		
	}
}

ReservedRoomId.java

package mrs.domain.model;

import java.io.Serializable;
import java.time.LocalDate;

import javax.persistence.Embeddable;

@Embeddable
public class ReservableRoomId implements Serializable {
	
	private Integer roomId;
	private LocalDate reservedDate;
	
	public ReservableRoomId(Integer roomId, LocalDate reservedDate) {
		this.roomId = roomId;
		this.reservedDate = reservedDate;
	}
	
	public ReservableRoomId() {
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((reservedDate == null)? 0 : reservedDate.hashCode());
		result = prime * result + ((roomId == null) ? 0 : roomId.hashCode());
		return result;
	}
	
	@Override
	public boolean equals(Object obj) {
		if(this == obj) return true;
		if(obj == null) return false;
		
		if(getClass() != obj.getClass()) return false;
		ReservableRoomId other = (ReservableRoomId) obj;
		if(reservedDate == null) {
			if(other.reservedDate != null) return false;
		} else if (!reservedDate.equals(other.reservedDate))
			return false;
		if(roomId == null) {
			if(other.roomId != null) return false;
		} else if (!roomId.equals(other.roomId))
			return false;
		return false;
	}
}

Reservation.java

package mrs.domain.model;

import java.io.Serializable;
import time.LocalTime;

import javax.persistence.*;

@Entity
public class Reservation implements Serializable {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer reservationId;
	
	private LocalTime startTime;
	private LocalTime endTime;
	
	@ManyToOne;
	@JoinColumns({@JoinColumn(name = "reserved_date"),
		@JoinColumn(name = "room_id")})
	private RservableRoom reservableRoom;
	
	@ManyToOne
	@JoinColumn(name="user_id")
	private User user;
}

[postgres] macにインストール

Spring Boot

$ postgres –version
-bash: postgres: command not found
$ brew search postgresql
==> Formulae
postgresql postgresql@11 postgresql@9.4 postgresql@9.6
postgresql@10 postgresql@12 postgresql@9.5
==> Casks
navicat-for-postgresql

最新版をインストールします。
$ brew install postgresql
To have launchd start postgresql now and restart at login:
brew services start postgresql
Or, if you don’t want/need a background service you can just run:
pg_ctl -D /usr/local/var/postgres start

$ postgres –version
postgres (PostgreSQL) 13.1
$ pg_ctl -D /usr/local/var/postgres start

# ユーザ作成
$ createuser -P root
# データベース作成
$ createdb test -O root
$ psql -l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
———–+——-+———-+———+——-+——————-
postgres | mac | UTF8 | C | C |
template0 | mac | UTF8 | C | C | =c/mac +
| | | | | mac=CTc/mac
template1 | mac | UTF8 | C | C | =c/mac +
| | | | | mac=CTc/mac
test | root | UTF8 | C | C |
(4 rows)

-> “test”が作成された

### psqlログイン
$ psql -U root test
psql (13.1)
Type “help” for help.

test=>

なんか異常にシンプルやなー

[Spring Boot][java] データアクセス

pom.xml

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>

jdbcTemplate使用
MessagesController.java

package com.example.demo;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("messages")
public class MessagesController {
	@Autowired
	JdbcTemplate jdbcTemplate;
	
	@RequestMapping(method = RequestMethod.GET)
	public List<Message> getMessages(){
		return jdbcTemplate.query("SELECT text FROM messages ORDER BY id", (rs, i)->{
			Message m = new Message();
			m.setText(rs.getString("text"));
			return m;
		});
	}
	
	@RequestMapping(method = RequestMethod.POST)
	public Message postMessage(@RequestBody Message message) {
		jbdcTemplate.update("INSERT INTO messages(text) VALUES (?)", message.getText());
		
	}
}

spring bootはクラスパス直下にschema.sqlが存在すると、起動時にそのsqlを実行する

CREATE TABLE messages (
	id INT PRIMARY KEY AUTO_INCREMENT,
	text VARCHAR(255)
);

### PostgreSQLを使う方法

		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
			<scope>runtime</scope>
		</dependency>

application.properties

string.datasource.username=root
string.datasource.password=hoge
spring.datasource.url=jdbc:postgresql://localhost:5432/spring

mysqlを使用する場合は、schema.sqlを用意するのが定石か

[Spring Boot][java] RESTfulのサンプル作成

Message.java

import java.io.Serializable;

public class Message implements Serializable {
	private String text;
	
	public String getText() {
		return text;
	}
	
	public void setText(String text) {
		this.text = text;
	}
}

MessagesController.java

package com.example.demo;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("messages")
public class MessagesController {
	final List<Message> messages = new CopyOnWriteArrayList<>();
	
	@RequestMapping(method = RequestMethod.GET)
	
	public List<Message> getMessages(){
		return messages;
	}
	
	@RequestMapping(method = RequestMethod.POST)
	public Message postMessages(@RequestBody Message message) {
		messages.add(message);
		return message;
	}
}

### 画面遷移型アプリケーション
テンプレートエンジンとの連携ライブラリ(Starterプロジェクト、AutoConfigure)が用意
– Tymeleaf, FreeMarker, Groovy templates, Velocity, Mustacheなど

Tymeleafを使用する
pom.xml

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

HelloController.java

package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HelloController {
	@RequestMapping("/hello")
	public String hello(Model model) {
		model.addAttribute("hello", "Hello World!");
		return "hello";
	}
}

/src/main/resources/templates/hello.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
	<meta charset="UTF-8" />
	<title></title>
</head>
<body>
	<p>
		<span th:text="${hello}">Hello!</span>
	</p>
</body>

src/main/resources

[Spring Boot][java] getting started

やっときました。Spring Boot。
まず、Spring Initializrを使います。
dependenciesに「Web」と入力します。

demo.zipをdownloadします。
STSでfile->import->maven->existing maven projects

DemoApplication.java

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);
	}
}

Run As -> Java ApplicationでTomcatが起動する

2021-01-28 22:00:38.906 INFO 6756 — [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-01-28 22:00:38.906 INFO 6756 — [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1111 ms
2021-01-28 22:00:39.057 INFO 6756 — [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService ‘applicationTaskExecutor’
2021-01-28 22:00:39.219 INFO 6756 — [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ”

controllerの機能を実装する

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class DemoApplication {

	@RequestMapping("/")
	String hello() {
		return "Hello World";
	}
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

}

Web server failed to start. Port 8080 was already in use.

$ lsof -i:8080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 6756 mac 41u IPv6 * 0t0 TCP *:http-alt (LISTEN)

src/main/resources/application.properties

server.port=8090

やべ、ちょっと感動した。
– MVC自動設定、Tomcat自動設定、CharacterEncodingFilter自動設定、Logback自動設定、Bean Validation自動設定

Amazon, 楽天, Yahooのショッピングカートの送料のアルゴリズム

Amazon, 楽天, Yahooのショッピングカートの送料のアルゴリズムを確認したい。

### Amazon
商品ごとに送料が計算され、合計金額で合計の送料が計算される

### 楽天
商品ごとに送料計算

### Yahoo Shopping
商品ごとに送料があり、注文手続きの際に、合計金額と合計送料を計算

なるほど、送料は基本商品ごとに持っていて、単純に合計するのね。
同じ会社の商品の場合の計算方法がよくわからん。例えば、A社から送料300円の商品Bと商品Cを購入した時って、送料600円にならないよね。。

うーむ。つまり、商品ごとに送料を持っていて、同じ会社から複数商品を買った場合は、計算処理してるってことね。

[WordPress][vagrant] オリジナルテーマ作成手順5

固定ページ(page.php)をコピーしてsingle.phpを作ります。

single.php

<?php get_header(); ?>
<!-- 上記が追記するコード -->
	<?php if(have_posts()):
		while(have_posts()): the_post();?>
 <section id="content">
	<div class="main">
	<h1><?php the_title(); ?></h1>
	<?php the_content(); ?>
	</div>
 </section>
<?php endwhile;
	endif;?>
 <!-- 下記が追記するコード -->
<?php get_footer(); ?>

### 日付別表示
date.php -> archive.php -> index.php

archive.php

<?php get_header(); ?>
<!-- 上記が追記するコード -->
 <section id="content">
	<div class="main">
      <?php 
      if ( have_posts() ) :
          while ( have_posts() ) : the_post();
      ?>
          <h2>
            <a href="<?php echo get_permalink(); ?>"><?php the_title(); ?></a>
          </h2>
          <section>
            <p>作成日時:<?php the_time('Y年n月j日'); ?></p>
            <a href="<?php echo get_permalink(); ?>"><?php the_excerpt(); ?></a>
          </section>
          <hr>
      <?php 
          endwhile;
      endif;
      ?>
	</div>
 </section>
 <!-- 下記が追記するコード -->
<?php get_footer(); ?>

固定ページやニュースの更新の作り方がなんとなくわかった。
ちょっとノウハウ入りそうだけど、自由にメンテナンスできるのは良いね。

[WordPress][vagrant] オリジナルテーマ作成手順4

作成手順3の続きです。
テンプレートには「優先順位」があり、優先度の高いテンプレートが優先的に使われる。
front-page.php->home.php->index.php

固定ページ
カスタムテンプレート -> page-{スラッグ名}.php -> page-{ID}.php -> page.php -> index.php

投稿ページ
single-{post-type}.php -> single.php -> index.php

### 固定ページの作り方
page.phpを作ります。

<?php get_header(); ?>
<!-- 上記が追記するコード -->
 <section id="content">
	<div class="main">
	<h1></h1>
	</div>
 </section>
 <!-- 下記が追記するコード -->
<?php get_footer(); ?>

classic editorをinstallして昔のエディタに直します。

管理画面の内容が反映されるように、page.phpを回収

<?php get_header(); ?>
<!-- 上記が追記するコード -->
 <section id="content">
	<div class="main">
	<h1><?php the_title(); ?></h1>
	<?php the_content(); ?>
	</div>
 </section>
 <!-- 下記が追記するコード -->
<?php get_footer(); ?>

管理画面で設定した内容が簡単に反映されるようになる。

続いて、投稿ページを作成していきたい。

[WordPress][vagrant] オリジナルテーマ作成手順3

作成手順2からの続きです。
css, imgが読み取れてないので、パスにget_template_directory_uri();を追加します。

index.php

<link rel="stylesheet" href="<?php echo get_template_directory_uri(); ?>/style.css">
// 省略
<img src="<?php echo get_template_directory_uri(); ?>/butterfly.jpg">

wp_headとwp_footerの追加

<?php wp_head(); ?>
<?php wp_footer(); ?>

ヘッダ上部に管理バーが表示されるようになる。

テンプレートは、header, footer, sidebar, indexなどに分割して、get_header();、get_footer();、get_sidebar();で呼び出すことができる。

header.php

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<link rel="stylesheet" href="style.css">
	<title>Document</title>
</head>
<body>
<div class="wrapper">
	<header>
		<h1 class="headline">
			<a href="">Sample</a>
		</h1>
		<ul class="nav-list">
			<li class="nav-list-item"><a href="">Home</a></li>
			<li class="nav-list-item"><a href="">About</a></li>
			<li class="nav-list-item"><a href="">Topic</a></li>
		</ul>
	</header>

footer.php

	<footer>
		<ul class="footer-menu">
			<li>home |</li>
			<li>about |</li>
			<li>service |</li>
			<li>Contact Us</li>
		</ul>
		<p>&copy; All rights reserved by Sample</p>
	</footer>
</div>
<?php wp_footer(); ?>
</body>
</html>

index.php

<?php get_header(); ?>
<!-- 上記が追記するコード -->
 
 <section id="content">
	<div class="main">
	<img src="<?php echo get_template_directory_uri(); ?>/butterfly.jpg">
	<h1>Butterfly</h1>
	<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
	<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>

	</div>
 </section>
 <!-- 下記が追記するコード -->
<?php get_footer(); ?>

テーマを作って、共通化するところまではきました。ここまでは特にそんなにWordPressの恩恵はありません。
固定ページ、投稿ページを作っていきたい。