タイトルの組み合わせで動作するアプリを作りました。
フォルダ構成と、用意すべきクラスは自分の理解の範疇で設定しています。
レイヤー構造ってどこまで用意すればいいんだろうなー。
フォルダ構成
アプリフォルダ
│
├─build.gradle
│
├─src/main
│ ├─java
│ │ └─com
│ │ └─example
│ │ │ SpaTestApplication.java : SpringBootApp
│ │ │
│ │ ├─config
│ │ │ MyConfiguration.java : JavaConfig
│ │ │
│ │ ├─domain
│ │ │ Item.java : 業務データモデル
│ │ │
│ │ ├─dto
│ │ │ DtoItem.java : DBレコード
│ │ │
│ │ ├─mapper
│ │ │ ItemMapper.java : (テーブルとSQLの?)マッパー
│ │ │
│ │ ├─repository
│ │ │ ItemRepository.java : ドメインとDTOの間を吸収する
│ │ │
│ │ ├─service
│ │ │ ItemService.java : ドメインを使ってビジネスロジックを処理する
│ │ │
│ │ └─web
│ │ └─rest
│ │ ItemResource.java : RESTコントローラ
│ │
│ └─resources
│ ├─config
│ │ mybatis.xml : MyBatisの設定内容
│ │
│ ├─sql
│ │ ItemMapper.xml : ItemMapperに対応するSQL群
│ │
│ └─static
│ │ index.html : ページ
│ │
│ ├─css
│ │ (割愛)
│ │
│ ├─js
│ │ (割愛)
│ │
│ └─vendor
│ ├─bootstrap
│ │ (割愛)
│ │
│ ├─Ink
│ │ (割愛)
│ │
│ └─jquery
│ (割愛)
│
└─src/test
└─java
└─com
└─example
(割愛)
Gradle
この記事として必要なものは
- org.mybatis.spring.boot:mybatis-spring-boot-starter:1.1.1
- org.postgresql:postgresql:9.4.1209
- org.springframework.boot:spring-boot-starter-web
くらいかな?
build.gradle
buildscript {
ext {
springBootVersion = '1.4.0.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'spring-boot'
jar {
baseName = 'demo'
version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile "javax.inject:javax.inject:${javax_inject_version}"
// DBまわり
compile('org.springframework:spring-tx:4.1.5.RELEASE')
compile('org.springframework:spring-jdbc:4.1.5.RELEASE')
compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.1.1')
compile('org.postgresql:postgresql:9.4.1209')
// 必須
compile('org.projectlombok:lombok')
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
eclipse {
classpath {
containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
}
}
bootRun {
addResources = true
}
task wrapper(type: Wrapper) {
gradleVersion = '3.0'
}
SPA部分
アプリ
SpaTestApplication.java
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpaTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpaTestApplication.class, args);
}
}
RESTコントローラ
SpaTestApplication.java
package com.example.web.rest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.example.domain.Item;
import com.example.service.ItemService;
import org.springframework.web.bind.annotation.*;
import javax.inject.Inject;
@RestController
@RequestMapping("/api")
public class ItemResource {
@Inject
private ItemService itemService;
@RequestMapping("/getheader")
public Map<String, Object> getHeader() {
Map<String, Object> model = new HashMap<String, Object>();
model.put("id", UUID.randomUUID().toString());
model.put("content", "Hello World! Yahoo!");
return model;
}
@RequestMapping("/items")
public List<Item> getItems() {
return itemService.getItems();
}
@RequestMapping(value="/items", method=RequestMethod.POST)
public Map<String, Object> postItem(@RequestBody Item item) {
Map<String, Object> model = new HashMap<String, Object>();
int maxId = itemService.addItem(item);
model.put("maxId", maxId);
return model;
}
@RequestMapping(value="/items/{id}", method=RequestMethod.PUT)
public Map<String, Object> putItem(@RequestBody Item item) {
Map<String, Object> model = new HashMap<String, Object>();
Item updated = itemService.updateItem(item);
model.put("updated", updated);
return model;
}
@RequestMapping(value="/items", method=RequestMethod.DELETE)
public Map<String, Object> deleteItem(@RequestHeader("Delete-Target") int id) {
Map<String, Object> model = new HashMap<String, Object>();
itemService.deleteItem(id);
model.put("deletedId", id);
return model;
}
}
ドメイン
RESTコントローラから登場したので、ここで記載。
Item.java
package com.example.domain;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* Created by saiki on 2016/09/01.
*/
@Data
public class Item {
@Getter
@Setter
private int id;
@Getter
@Setter
private String name;
@Getter
@Setter
private String comment;
@Getter
@Setter
private Date updateAt;
}
Getter,Setterはlombokで記述しています。
サービス
ItemService.java
package com.example.service;
import com.example.domain.Item;
import com.example.repository.ItemRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.inject.Inject;
import java.util.List;
@Service
@Transactional
public class ItemService {
@Inject
private ItemRepository itemRepository;
@Transactional(readOnly = true)
public List<Item> getItems() {
return itemRepository.getItems();
}
@Transactional
public int addItem(Item item) {
itemRepository.addItem(item);
return itemRepository.getMaxId();
}
@Transactional
public Item updateItem(Item item) {
itemRepository.updateItem(item);
return itemRepository.getItem(item.getId());
}
@Transactional
public void deleteItem(int id) {
itemRepository.deleteItem(id);
}
}
リポジトリ
package com.example.repository;
import com.example.domain.Item;
import com.example.dto.DtoItem;
import com.example.mapper.ItemMapper;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
@Repository
public class ItemRepository {
@Inject
private ItemMapper itemMapper;
public List<Item> getItems() {
List<DtoItem> dto = itemMapper.list();
List<Item> domain = new ArrayList<Item>();
dto.stream().forEach(e -> {
Item item = new Item();
item.setId(e.getId());
item.setName(e.getName());
item.setComment(e.getComment());
item.setUpdateAt(e.getUpdateAt());
domain.add(item);
});
return domain;
}
public void addItem(Item item) {
DtoItem dto = new DtoItem();
dto.setName(item.getName());
dto.setComment(item.getComment());
itemMapper.add(dto);
}
public int getMaxId() {
return itemMapper.getMaxId();
}
public void updateItem(Item item) {
DtoItem dto = new DtoItem();
dto.setId(item.getId());
dto.setName(item.getName());
dto.setComment(item.getComment());
itemMapper.update(dto);
}
public Item getItem(int id) {
DtoItem dto = itemMapper.getOne(id);
Item domain = new Item();
domain.setId(dto.getId());
domain.setName(dto.getName());
domain.setComment(dto.getComment());
domain.setUpdateAt(dto.getUpdateAt());
return domain;
}
public void deleteItem(int id) {
itemMapper.delete(id);
}
}
DTO
リポジトリにたどり着いたので、DTOが出現。
DtoItem.java
package com.example.dto;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* Created by saiki on 2016/09/02.
*/
@Data
public class DtoItem {
@Getter
@Setter
private int id;
@Getter
@Setter
private String name;
@Getter
@Setter
private String comment;
@Getter
@Setter
private Date updateAt;
}
データベースアクセス
ここからMyBatisのアノテーションが出てくるので、MyBatisの設定をします。
MyBatisの設定
package com.example.config;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
/**
* Created by saiki on 2016/09/01.
*/
@Configuration
public class MyConfiguration {
@Autowired
@Bean
public DataSourceTransactionManager transactionManager(
@Qualifier("dataSource") DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
@Autowired
@Bean
public SqlSessionTemplate sqlSessionTemplate(
@Qualifier("dataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(
new DefaultResourceLoader());
// MyBatis のコンフィグレーションファイル
bean.setConfigLocation(resolver.getResource("classpath:config/mybatis.xml"));
// MyBatis で使用する SQL ファイル群
bean.setMapperLocations(resolver.getResources("classpath:sql/*.xml"));
return new SqlSessionTemplate(bean.getObject());
}
@Primary
@Autowired
@Bean
public DriverManagerDataSource dataSource() {
// 今回の例は ORACLE、適宜変更する
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(org.postgresql.Driver.class.getName());
dataSource.setUrl("jdbc:postgresql://localhost:5432/mybatis");
dataSource.setUsername("mybatisuser");
dataSource.setPassword("mybatispass");
return dataSource;
}
}
mybatis.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- アンダースコア区切り (スネークケース) のカラム名をキャメルケースにマップする設定 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- SQL 内で AS によって設定された列名をマップする設定 -->
<setting name="useColumnLabel" value="true"/>
</settings>
</configuration>
MyBatisの設定はここにありました
http://www.mybatis.org/mybatis-3/ja/configuration.html
マッパー
ItemMapper.java
package com.example.mapper;
import com.example.dto.DtoItem;
import org.apache.ibatis.annotations.*;
import java.util.List;
/**
* Created by saiki on 2016/09/01.
*/
@Mapper
public interface ItemMapper {
List<DtoItem> list();
void add(DtoItem dto);
int getMaxId();
void update(DtoItem dto);
DtoItem getOne(int id);
void delete(int id);
}
ItemMapper.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.mapper.ItemMapper">
<select id="list" resultType="com.example.dto.DtoItem">
SELECT
id,
name,
comment,
update_at
FROM item
ORDER BY update_at DESC
</select>
<insert id="add" parameterType="com.example.dto.DtoItem">
INSERT INTO item (
name,
comment
)
VALUES (
#{name},
#{comment}
)
</insert>
<select id="getMaxId" resultType="int">
SELECT
max(id)
FROM item
</select>
<update id="update" parameterType="com.example.dto.DtoItem">
UPDATE item
SET name = #{name}, comment = #{comment}, update_at = now()
WHERE id = #{id}
</update>
<select id="getOne" parameterType="int" resultType="com.example.dto.DtoItem">
SELECT
id,
name,
comment,
update_at
FROM item
WHERE id = #{id}
</select>
<insert id="delete" parameterType="int">
DELETE
FROM item
WHERE id=#{Id}
</insert>
</mapper>
よくわからんかったところ
- @Configration(Bean)でMyBatisの設定を行う(アノテーションだから適用タイミングがイメージできなかった)
- "マッパー"って広く使われすぎててググってもようわからん
- そもそもBean、DIに慣れてない
総評
もっとがんばりましょう