2018年12月21日 星期五

Spring Boot + Spring Data Mongodb



1.架設MongoDB
使用docker compose把MongoDB環境建立起來,並將container放到對應的network(iwlp-network).以便其他的container調用

container使用HOST(宿主)時區
  volumes:
      - /etc/localtime:/etc/localtime:ro

docker-compose.yml
version : '3'
services:
  mongodb:
    image: 'mongo:3.2.21-jessie'
    container_name: iwlp-mongodb
    ports:
      - "27017:27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: username
      MONGO_INITDB_ROOT_PASSWORD: password
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./containers/mongodb/iwlp-mongodb:/data/db
    networks:
      - iwlp-network
networks:
  iwlp-network:
    driver: bridge

留意紅字的地方,換上自己的設定即可

2.建設MongoDB Web UI
有了MongoDB後,首要任務為需要一個簡單好用的GUI可以操作MongoDB
這裡使用Mongo-express,重要的設定參數為
ME_CONFIG_MONGODB_ADMINUSERNAME: mangodb的連線帳號
ME_CONFIG_MONGODB_ADMINPASSWORD: mangodb的連線密碼
ME_CONFIG_MONGODB_SERVER: mangodb的container名稱
depends_on: 設定container的相依容器,dockercompose會依據依賴順序啟動服務

因此修改一下原有的docker-compose.yml,並加入mongodb-ui
version : '3'
services:
  mongodb:
    image: 'mongo:3.2.21-jessie'
    container_name: iwlp-mongodb
    ports:
      - "27017:27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: username
      MONGO_INITDB_ROOT_PASSWORD: password
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./containers/mongodb/iwlp-mongodb:/data/db
    networks:
      - iwlp-network
      
  mongodb-ui:
    image: 'mongo-express:0.49'
    container_name: iwlp-mongodb-ui
    ports:
      - "27081:8081"
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: username
      ME_CONFIG_MONGODB_ADMINPASSWORD: password
      ME_CONFIG_MONGODB_SERVER: iwlp-mongodb
    depends_on:
      - mongodb
    volumes:
      - /etc/localtime:/etc/localtime:ro
    networks:
      - iwlp-network
networks:
  iwlp-network:
    driver: bridge
version : '3'
services:
  mongodb:
    image: 'mongo:3.2.21-jessie'
    container_name: iwlp-mongodb
    ports:
      - "27017:27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: username
      MONGO_INITDB_ROOT_PASSWORD: password
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./containers/mongodb/iwlp-mongodb:/data/db
    networks:
      - iwlp-network
  mongodb-ui:
    image: 'mongo-express:0.49'
    container_name: mongodb-ui
    ports:
      - "27081:8081"
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: username
      ME_CONFIG_MONGODB_ADMINPASSWORD: password
      ME_CONFIG_MONGODB_SERVER:iwlp-mongodb
   depends_on:
      - iwlp-mongodb
    volumes:
      - /etc/localtime:/etc/localtime:ro
    networks:
      -iwlp-network  
networks:
  iwlp-network:
    driver: bridge

進入到專案的目錄{usweHome}/microservices/iwlp
執行docker compose指令,執行yml檔內所描述的微服務
sudo docker-compose up

透過以下連結進入管理頁面
http://{domainName}:27081/
{domainName}請換成你的docker IP或 domain name


3.建立Java專案

利用STS建立Java專案

預設目錄結構
org.iwlp.config
org.iwlp.model
org.iwlp.model.repository
org.iwlp.controller
org.iwlp.service

3.1 設定需要的library
build.gradle
buildscript {
       ext {
             springBootVersion = '2.1.1.RELEASE'
       }
       repositories {
             mavenCentral()
       }
       dependencies {
              classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
       }
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'org.iwlp'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
       mavenCentral()
}
dependencies {
       implementation('org.springframework.boot:spring-boot-starter-data-mongodb')
       implementation('org.springframework.boot:spring-boot-starter-web')
       testImplementation('org.springframework.boot:spring-boot-starter-test')
       
       // lombok
       annotationProcessor('org.projectlombok:lombok')
       compileOnly('org.projectlombok:lombok')
       testAnnotationProcessor('org.projectlombok:lombok')
       testCompileOnly('org.projectlombok:lombok')
       
       // gson
       compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
       
       // commons-lang3
       compile group: 'org.apache.commons', name: 'commons-lang3', version:  '3.7'
       
       // swagger2
       compile group: 'io.springfox', name: 'springfox-swagger2', version:  '2.9.2'
       compile group: 'io.springfox', name: 'springfox-swagger-ui', version:  '2.9.2'
}


3.2 設定datasouce和專案相關配置
application.properties
#datasource
spring.data.mongodb.host=211.20.109.45
spring.data.mongodb.username=username
spring.data.mongodb.password=password
spring.data.mongodb.database=organization_DB
spring.data.mongodb.authentication-database=admin
#domain
server.port=8080
server.servlet.context-path=/iwlp-mongodb/
spring.application.name=iwlp-mongodb
spring.jmx.default-domain=iwlp-mongodb
endpoints.jmx.domain=iwlp-mongodb
endpoints.jmx.uniqueNames=true


3.3 設定Swagger配置
org.iwlp.config.Swagger2Config
package org.iwlp.config;
import java.util.Collections;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class Swagger2Config {
    private static final String SWAGGER2_TITLE = "我要努力偷學程式碼 - 角色權限管理管理 API 文件";
    private static final String DESCRIPTION = "提供角色管理 OpenApi 文件";
    private static final String CONTACT = "我要努力偷學程式碼";
    private static final String EMAIL = "arder1986@gmail.com";
    private static final String URL = "";
    private static final String VERSION = "2.0";
    @Bean
    public Docket newsApi() {
        return new  Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.any()).paths(PathSelectors.any()).build();
    }
    private ApiInfo apiInfo() {
        Contact contact = new Contact(CONTACT, URL, EMAIL);
        return new ApiInfo(SWAGGER2_TITLE, DESCRIPTION, VERSION, "TERMS OF  SERVICE URL", contact, "LICENSE", "LICENSE URL", Collections.emptyList());
    }
}


3.4 設定cross domain
org.iwlp.config.WebConfig
package org.iwlp.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**").allowedHeaders("*").allowedMethods("*").allowedOrigins("*");
                registry.addMapping("/v2/**").allowedHeaders("*").allowedMethods("*").allowedOrigins("*");
            }
        };
    }
}

3.5 Document(entity)
建立Role(Document),利用lombok簡化程式碼,自動替類別加入getter/setter

org.iwlp.model.Role
package org.iwlp.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel(description = "Role Class 表示角色完整資訊")
@Document(collection = "role")
public class Role {
    @ApiModelProperty(notes = "系統使用的唯一識別碼", required = true, position = 0)
    @Id
    private String id;
    @ApiModelProperty(notes = "角色名稱", required = true, position = 2)
    private String name;
    @ApiModelProperty(notes = "角色描述", required = false, position = 3)
    private String description;
}



3.6 Repository
org.iwlp.model.repository.RoleRepository
package org.iwlp.model.repository;

import org.iwlp.model.Role;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;

public interface RoleRepository extends MongoRepository<Role, String>{
    public Page<Role> findByName(Pageable pageable, String name);
}

3.7 Service
建立Service
核心的操作都會在這類別內開發
org.iwlp.service.RoleService
package org.iwlp.service;
import org.iwlp.exception.NotFoundException;
import org.iwlp.model.Role;
import org.iwlp.model.repository.RoleRepository;
import org.iwlp.utils.PageableUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
@Service
public class RoleService {
    private static final String ROLE_NOT_FOUND_EXCEPTION_MESSAGE = "role不存在";
    
    @Autowired
    private RoleRepository roleRepo;
    
    public Role createRole(Role role) throws Exception {
        role.setId(null);
        return roleRepo.save(role);
    }
    
    public Role updateRole(String id, Role role) {
        Role entity = getRole(id);
        entity.setId(id);
        entity.setName(role.getName());
        return roleRepo.save(entity);
    }
    public Role getRole(String id) {
        StringBuilder msgSb = new StringBuilder("doucment  ").append(ROLE_NOT_FOUND_EXCEPTION_MESSAGE).append(", id:").append(id);
        return roleRepo.findById(id).orElseThrow(() -> new  NotFoundException(msgSb.toString()));
    }
    
    public void deleteRole(String id) {
        Role entity = getRole(id);
        roleRepo.delete(entity);
    }
    
    public Page<Role> searchRole(String name, Integer page, Integer size,  Sort.Direction direction, String properties) {
        Pageable pageable = PageableUtils.initPageable(page, size, direction,  properties);
        Page<Role> records = null;
        
        if( StringUtils.isEmpty(name)) {
            records = roleRepo.findAll(pageable);
        } else {
            records = roleRepo.findByName(pageable, name);
        }
        return records;
    }
}


3.8 建立Controller
撰寫對外的REST API
org.iwlp.controller.Controller
package org.iwlp.controller;
import org.iwlp.model.Role;
import org.iwlp.service.RoleService;
import org.iwlp.utils.ThrowableUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
@Api(value = "/v2/", description = "角色管理", produces = "application/json")
@RestController
@RequestMapping(value = "/api")
public class RoleController {
    @Autowired
    private RoleService roleServer;
    
    @RequestMapping(method=RequestMethod.POST,value="/role",  headers="Content-Type=application/json")
    @ApiOperation(value = "新增角色",notes="新增角色")
    @ApiImplicitParams({
    })
    public Object createRole(
            @RequestBody Role role
            ){
        try {
            return roleServer.createRole(role);
        } catch (Exception e) {
            return ThrowableUtils.getThrowableResponseEntityAndLog(e);
        }
    }
    
    @RequestMapping(method=RequestMethod.PUT,value="/role/{id}",  headers="Content-Type=application/json")
    @ApiOperation(value = "修改角色",notes="修改角色")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "id", value = "role id", required = true,  defaultValue = "",dataType = "string", paramType = "path"),
    })
    public Role updateRole(
            @PathVariable (name="id") String id,
            @RequestBody Role role
            ){
        return roleServer.updateRole(id, role);
    }
    
    @RequestMapping(method=RequestMethod.GET,value="/role/{id}")
    @ApiOperation(value = "取得角色",notes="取得角色")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "id", value = "role id", required = true,  defaultValue = "",dataType = "string", paramType = "path"),
    })
    public Role getRole(
            @PathVariable (name="id") String id
            ){
        return roleServer.getRole(id);
    }
    
    @RequestMapping(method=RequestMethod.DELETE,value="/role/{id}")
    @ApiOperation(value = "刪除角色",notes="刪除角色")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "id", value = "role id", required =  true,dataType = "string", paramType = "path"),
    })
    public Object deleteRole(
            @PathVariable (name="id") String id
            ){
        try {
            roleServer.deleteRole(id);
            return true;
        } catch (Exception e) {
            return ThrowableUtils.getThrowableResponseEntityAndLog(e);
        }
    }
    
    @RequestMapping(method=RequestMethod.GET,value="/roles")
    @ApiOperation(value = "分頁搜尋所有角色",notes="分頁搜尋所有角色")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "page", value = "page", required = false,  example = "1",dataType = "inteage", paramType = "query"),
        @ApiImplicitParam(name = "size", value = "size", required = false,  example = "10" ,dataType = "int", paramType = "query"),
        @ApiImplicitParam(name = "direction", value = "direction", required =  false, defaultValue = "ASC",dataType = "string", paramType = "query"),
        @ApiImplicitParam(name = "properties", value = "column name", required =  false, defaultValue = "id",dataType = "string", paramType = "query")
    })
    public Page<Role> search(
            @RequestParam(value = "page", required = false) Integer page,
            @RequestParam(value = "size", required = false) Integer size,
            @RequestParam(value = "direction", required = false) Sort.Direction  direction,
            @RequestParam(value = "properties", required = false) String  properties
            ){
        return roleServer.searchRole(null, page, size, direction,  properties);
    }
}

3.9 Swagger-ui
專案運行後,訪問Swagger-ui
http://localhost:8080/iwlp-mongodb/swagger-ui.html

角色管理的所有REST API


執行新增角色

透過WEB UI檢驗結果

4.原始程式碼
iwlp-mongodb.zip

5.本文參考
[1] Dockerhub mongodb, from https://hub.docker.com/_/mongo/
[2] Dockerhub mongo-express, from https://hub.docker.com/_/mongo-express
[3] 30-2之使用Docker來建構MongoDB環境, from https://ithelp.ithome.com.tw/articles/10184657
[4] Mongo-express Github Project, from https://github.com/mongo-express/mongo-express



https://www.evernote.com/l/AbE_xgynEyNNqoIn53j7reIZsW12rmxqmLM/