spring
MyBatis

SpringBoot(SPA)+MyBatis+RDBMS(PostgreSQL)

More than 1 year has passed since last update.

タイトルの組み合わせで動作するアプリを作りました。

フォルダ構成と、用意すべきクラスは自分の理解の範疇で設定しています。

レイヤー構造ってどこまで用意すればいいんだろうなー。


フォルダ構成

アプリフォルダ


├─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に慣れてない


総評

もっとがんばりましょう