[Spring Boot2.4.2] Postgresからselect

### Postgresにデータ挿入
psql -U root test
CREATE TABLE syain (
id SERIAL NOT NULL,
name varchar(255),
romaji varchar(255),
PRIMARY KEY(id)
);

INSERT INTO syain (name, romaji) VALUES (‘鈴木’,’suzuki’);
INSERT INTO syain (name, romaji) VALUES (‘田中’,’tanaka’);
INSERT INTO syain (name, romaji) VALUES (‘佐藤’,’sato’);
test=> select * from syain;
id | name | romaji
—-+——+——–
1 | 鈴木 | suzuki
2 | 田中 | tanaka
3 | 佐藤 | sato
(3 rows)

pom.xml

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
			<scope>runtime</scope>
		</dependency>

application.properties

spring.jpa.database=POSTGRESQL
spring.datasource.url=jdbc:postgresql://localhost:5432/test
spring.datasource.username=root
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.format_sql=true
spring.datasource.sql-script-encoding=UTF-8

Syain.java

package com.example.demo;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Id;

@Entity
@Table(name="syain")
public class Syain {
	@Id
	private Integer id;
	private String name;
	private String romaji;
	
	public Integer getId() {
		return id;
	}
	public String getName() {
		return name;
	}
	public String getRomaji() {
		return romaji;
	}
}

SyainRepository.java

package com.example.demo;
import org.springframework.data.jpa.repository.JpaRepository;

public interface SyainRepository extends JpaRepository<Syain, Integer>{
}

MainController.java

package com.example.demo;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MainController {
	@Autowired
	private SyainRepository syainRepository;
	
	@GetMapping("/test1")
	public List<Syain> get() {
		return syainRepository.findAll();
	}
}

うおおおおおおおおおおおおおおおおお
きたあああああああああああああああああああああああ

[Spring Boot2.4.2] フォームとエンティティの連携

エンティティとは?
-> relational databaseの表を表す

src/main/resources/templates/test1/index.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<title>check</title>
</head>
<body >
<form method="post" action="#" th:action="@{/test1/testform}" th:object="${test1Form}">
<p><input type="text" id="id" name="id" th:field="*{id}"/></p>
<p><input type="text" id="name" name="name" th:field="*{name}"></p>
<p><input type="submit" value="送信ボタン"></p>
</form>
</body>
</html>

フォームクラス: Test1Form.java
L controllerのmodel.addAttribute(“test1Form”, new Test1Form());でvalidationをかけている

package com.example.demo;

public class Test1Form {
	private Integer id;
	private String name;
	
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

エンティティのクラス: Syain.java (社員)
L formのvalidationと同じく、privateで宣言して、geter, setterを書く
L getter, setterは、データを外部から取得するメソッド、あるいはそのデータを外部から変更するメソッド

package com.example.demo;

public class Syain {
	private Integer id;
	private String name;
	
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

MainController.java

package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Controller
@RequestMapping("/test1")
public class MainController {
	@Autowired
	private SyainRepository syainRepository;
	
    @GetMapping
    public String disp1(
    		Model model) {
    	model.addAttribute("test1Form", new Test1Form());
        return "test1/index";
    }
    @PostMapping("/testform")
	public String disp2(Test1Form test1Form) {
		    Syain syain = new Syain();
		    syain.setId(test1Form.getId());
		    syain.setName(test1Form.getName());
		    syainRepository.insertSyain(syain);
			return "redirect:/test1/";
		}
}

pom.xml

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

SyainRepository.java

package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class SyainRepository {
	private final JdbcTemplate jdbcTemplate;
	
	@Autowired
	public SyainRepository(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}
	
	public void insertSyain(Syain syain) {
		jdbcTemplate.update("INSERT INTO syain(id,name) VALUES (?, ?)",
				syain.getId(), syain.getName());
	}
}

Description:

Failed to configure a DataSource: ‘url’ attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class

なるほど、何となくわかってきた。

[Spring Boot2.4.2] postgresのInsert

src/main/java/com.example.demo.dto
Customer.java

package com.example.demo.dto;

import javax.validation.constraints.NotNull;

public class Customer {
	@NotNull
	private String id;
	
	@NotNull
	private String username;
	
	@NotNull
	private String email;
	
	@NotNull
	private String phoneNumber;
	
	@NotNull
	private String postCode;
}

src/main/java/com.example.demo.repository
CustomerMapper.java
L @Mapperアノテーションを作る
L intは件数、insertはcreate処理
L customerは実際にinsertするobject

package com.example.demo.repository;

import com.example.demo.dto.Customer;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface CustomerMapper {
	
	int insert(Customer customer);
}

src/main/java/com.example.demo.repository
CustomerMapper.xml
L mapper namespaceでMapperを宣言
L #{fieldName} でアクセスすることができる、jdbcType= で型を指定する

<?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.exmaple.demo.repository.CustomerMapper">
	<insert id="insert" parameterType="com.exmaple.demo.dto.Customer">
		INSERT INTO customer VALUES (
			#{id, jdbcType=VARCHAR},
			#{username, jdbcType=VARCHAR},
			#{email, jdbcType=VARCHAR},
			#{phoneNumber, jdbcType=VARCHAR},
			#{postCode, jdbcType=VARCHAR}
		)
	</insert>
</mapper>

src/test/java/com.example.demo.config
DbConfig.java

package com.example.demo.config;

import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;

public class DbConfig {
	
	@Value("${spring.datasource.username}")
	private String username;
	
	@Value("${spring.datasource.password}")
	private String password;
	
	@Value("${spring.datasource.url}")
	private String url;
	
	@Value("${spring.datasource.driverClassName}")
	private String jdbcDriver;
	
	@Bean
	public DataSource dataSource() {
		return new TransactionAwareDataSourceProxy(
				DataSourceBuilder.create()
					.username(this.username)
					.password(this.password)
					.url(this.url)
					.driverClassName(this.jdbcDriver)
					.build());
	}
}

src/test/java/com.example.demo.service
CustomerService.java

import com.example.demo.dto.Customer;

public interface CustomerService {
	
	Customer register(Customer customer);
}

src/test/java/com.example.demo.service.impl

package com.example.demo.service.impl;

import com.example.demo.dto.Customer;
import com.example.demo.repository.CustomerMapper;
import com.example.demo.service.CustomerService;
import org.springframework.stereotype.Service;

@Service
public class CustomerServiceImpl implements CustomerService {
	
	private CustomerMapper mapper;
	
	public CustomerServiceImpl(CustomerMapper mapper) {
		this.mapper = mapper;
	}
	
	@Override
	public Customer register(Customer customer) {
		
		String formattedEmail = formatEmail(customer.getEmail());
		
		customer.setEmail(formattedEmail);
		
		mapper.insert(customer);
		return customer;
	}
	
	private String formatEmail(String email) {
		String[] separatedEmail = email.split("@");
		return separatedEmail[0] + "@" + separatedEmail[1].toLowerCase();
	}
}

src/test/java/com.example.demo.controller

package com.example.demo.controller;

import com.example.demo.dto.Customer;
import com.example.demo.service.CustomerService;
import org.springframework.validation.Errors;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/customers")
public class CustomerController {
	
	private CustomerService customerService;
	
	public CustomerController(CustomerService customerService) {
		this.customerService = customerService;
	}
	
	@PostMapping
	public Customer post(@Validated @RequestBody Customer customer, Errors errors) {
		
		if (errors.hasErrors()) {
			throw new RuntimeException((Throwable) errors);
		}
		
		return customerService.register(customer);
	}
}

POST man
body -> json

{
    "id": "011",
    "username": "user011",
    "email": "test.user.011@EXAMPLE.com",
    "phoneNumber": "12345678901",
    "postCode": "4567123"
}

なんやろ、上手く動作しないな。全体の流れは何となく理解したが、🤮🤮🤮

org.springframework.validation.BeanPropertyBindingResult cannot be cast to class java.lang.Throwable (org.springframework.validation.BeanPropertyBindingResult is in unnamed module of loader ‘app’

[Spring Boot2.4.2] postgresのCRUDの前準備

Spring Bootのアーキテクチャ

CRUDの処理は、Mapper(Repository)クラスで行なっている。
Spring Initializrで雛形をgenerateする。
DependenciesにValidation, Spring Web, MyBatis, PostgreSQL Driveを追加する。
mybatisとは?
-> カスタムSQL、ストアドプロシージャ、高度なマッピング処理に対応

src/main/resources/application.yml

# Web
server:
  port: 8081
  servlet:
    context-path: /api

postgres/initdb/01_DDL_CREATE_TABLE.sql

CREATE TABLE customer (
	id VARCHAR(10) PRIMARY KEY,
	username VARCHAR(50) NOT NULL,
	email VARCHAR(50) NOT NULL,
	phone_number VARCHAR(11) NOT NULL,
	post_code VARCHAR(7) NOT NULL
);

postgres/initdb/02_DML_INSERT_INIT_DATA.sql

INSERT INTO customer VALUES ('001', 'user001', 'test.user.001@example.com', '12345678901', '1234567');
INSERT INTO customer VALUES ('002', 'user002', 'test.user.002@example.com', '23456789012', '2345671');
INSERT INTO customer VALUES ('003', 'user003', 'test.user.003@example.com', '34567890123', '3456712');
INSERT INTO customer VALUES ('004', 'user004', 'test.user.004@example.com', '45678901234', '4567123');
INSERT INTO customer VALUES ('005', 'user005', 'test.user.005@example.com', '56789012345', '5671234');
INSERT INTO customer VALUES ('006', 'user006', 'test.user.006@example.com', '67890123456', '6712345');
INSERT INTO customer VALUES ('007', 'user007', 'test.user.007@example.com', '78901234567', '7123456');
INSERT INTO customer VALUES ('008', 'user008', 'test.user.008@example.com', '89012345678', '1234567');
INSERT INTO customer VALUES ('009', 'user009', 'test.user.009@example.com', '90123456789', '2345671');
INSERT INTO customer VALUES ('010', 'user010', 'test.user.010@example.com', '01234567890', '3456712');

test=> select * from customer;
id | username | email | phone_number | post_code
—–+———-+—————————+————–+———–
001 | user001 | test.user.001@example.com | 12345678901 | 1234567
002 | user002 | test.user.002@example.com | 23456789012 | 2345671
003 | user003 | test.user.003@example.com | 34567890123 | 3456712
004 | user004 | test.user.004@example.com | 45678901234 | 4567123
005 | user005 | test.user.005@example.com | 56789012345 | 5671234
006 | user006 | test.user.006@example.com | 67890123456 | 6712345
007 | user007 | test.user.007@example.com | 78901234567 | 7123456
008 | user008 | test.user.008@example.com | 89012345678 | 1234567
009 | user009 | test.user.009@example.com | 90123456789 | 2345671
010 | user010 | test.user.010@example.com | 01234567890 | 3456712

application.yml
-> camel caseとsnake caseの命名の差分をMyBatis側で吸収し、適切にテーブル・カラム名とクラス・フィールド名をマッピングする。

# Datasource
spring:
  datasource:
    driverClassName: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/test
    username: root
    password:
    
# MyBatis
mybatis:
  configuration:
    map-underscore-to-camel-case: true

com.example.demo.controllerにCustomerController.javaを作る
CustomerController.java

package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CustomerController {
	
	@GetMapping("/hello")
	public String hello() {
		return "Hello World.";
	}
}


ここまでは基礎

build.gradleに依存性を追加

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.4'
	runtimeOnly 'org.postgresql:postgresql'
	testImplementation 'org.dbunit:dbunit:2.5.3'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}