LoginSignup
12
12

More than 5 years have passed since last update.

SpringBoot(SPA)+MyBatis+RDBMS(PostgreSQL)

Last updated at Posted at 2016-09-05

タイトルの組み合わせで動作するアプリを作りました。
フォルダ構成と、用意すべきクラスは自分の理解の範疇で設定しています。
レイヤー構造ってどこまで用意すればいいんだろうなー。

フォルダ構成

アプリフォルダ
│
├─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に慣れてない

総評

もっとがんばりましょう

12
12
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
12