over 2 years ago

隨著 Docker 開始 Microservices 這名詞也開始常聽到,所以就想換用 SpringBoot 簡化設定並封裝成單一 Jar,Docker 化步驟就簡單很多,這邊使用 Mybatis 整合

Java

Java版本 8

資料庫

以下是我範例資料庫是用PostgreSQL

userinfo.sql
CREATE TABLE "public"."userinfo" (
"serno" bigserial NOT NULL PRIMARY KEY,
"usertype" int4 NOT NULL,
"userid" varchar(50) NULL,
"timecreate" date
)

MyBatis

因為使用jdk8所以驅動要抓jdbc41喔
這邊抓 PostgreSQL JDBC Driver

再來需要 mybatis-generator-core-1.3.2.jar
這邊抓 The MyBatis Blog

再準備設定檔

generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!-- 資料庫驅動jar -->
    <classPathEntry location="./postgresql-9.4-1201.jdbc41.jar" />
    <context id="DB2Tables" targetRuntime="MyBatis3">
        <commentGenerator>  
         <!-- 去除自動生成的注釋 -->  
            <property name="suppressAllComments" value="true" />  
        </commentGenerator>
        <!-- 資料庫連接 -->
        <jdbcConnection
            driverClass="org.postgresql.Driver"
            connectionURL="jdbc:postgresql://127.0.0.1:5432/test"
            userId="root"
            password="123456">
        </jdbcConnection>
        <!-- false:JDBC DECIMAL、NUMERIC類型解析為Integer,默認方式 -->
        <!-- true:JDBC DECIMAL、NUMERIC類型解析為java.math.BigDecimal -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>
        <!-- 生成模型的包名和位置 (可以自訂位址,但是路徑不存在不會自動創建使用Maven生成在target目錄下,會自動創建) -->
        <javaModelGenerator targetPackage="com.sam.modules.entity"
            targetProject="src">
            <property name="enableSubPackages" value="false" />
            <property name="trimStrings" value="true" /><!-- 從數據庫返回的值被清理前後的空格  -->
        </javaModelGenerator>
        <!-- 生成的映射文件包名和位置 -->
        <sqlMapGenerator targetPackage="com.sam.modules.mappings"
            targetProject="src">
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>
        <!-- 生成DAO的包名和位置 -->
        <javaClientGenerator targetPackage="com.sam.modules.dao"
            targetProject="src" type="ANNOTATEDMAPPER">
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>
        <!-- tableName:用於自動生成代碼的資料庫表;domainObjectName:對應於資料庫表的javaBean類名 -->
        <table tableName="userinfo" schema="public" delimitIdentifiers="true" delimitAllColumns="true">
            <!-- 此欄位使用自動增加的型態,不需要加入到SQL語法 -->
            <generatedKey column="serno" sqlStatement="JDBC"/>
        </table>
    </context>
</generatorConfiguration>

跟之前比較不同的是 javaClientGenerator type 負責 orm 的部分由以前的 XMLMAPPER 改成 ANNOTATEDMAPPER,比較符合整個專案的風格

建立個src目錄再執行bat就可以產出了

generator.bat
REM set JAVA_HOME=D:\IDE\Java\jdk1.7.0_51
java -jar mybatis-generator-core-1.3.2.jar -configfile generatorConfig.xml -overwrite 

Spring Boot

開一個新的 Gradle 專案
先看一下設定檔

build.gradle
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.4.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'

sourceCompatibility = 1.8
version = '1.0'
jar {
    manifest {
        attributes 'Implementation-Title': 'Gradle Quickstart',
                   'Implementation-Version': version
        attributes 'Main-Class': 'com.sam.app.Application'
    }
    baseName = 'springboot-all'
}

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.springframework.boot:spring-boot-starter-web:1.2.4.RELEASE',
            'org.springframework.boot:spring-boot-starter-jdbc:1.2.4.RELEASE'
    compile 'org.postgresql:postgresql:9.4-1201-jdbc41'
    compile 'com.zaxxer:HikariCP:2.3.+'
    compile 'org.mybatis:mybatis:3.3.0',
            'org.mybatis:mybatis-spring:1.2.2'
    testCompile group: 'junit', name: 'junit', version: '4.+'
}

spring-boot 就不用說了一定要的
這次改用了 HikariCP 這個 DataSource,就是強調快而已啦,感覺還很新可以關注一下
HikariCP. It's faster.
brettwooldridge/HikariCP · GitHub

再把剛剛產出的mybatis通通丟到專案內

Spring Boot的啟動程式

Application.java
package com.sam.app;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.context.annotation.*;

//這邊使用 Java Class 作為設定,而非XML

@Configuration
//啟用 Spring Boot 自動配置,將自動判斷專案使用到的套件,建立相關的設定。

@EnableAutoConfiguration
//自動掃描 Spring Bean 元件

@ComponentScan( basePackages = {"com.sam"} )

//@SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScan

public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

SpringBootApplication這注解我試不出來,就還是用原先的方法

再來是設定檔

AppConfig.java
package com.sam.app;

import javax.servlet.Filter;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.web.filter.CharacterEncodingFilter;
import com.zaxxer.hikari.*;

@Configuration
@MapperScan("com.sam.modules.dao")
//@PropertySources({ @PropertySource(value = "classpath:application.properties", ignoreResourceNotFound = true), @PropertySource(value = "file:./application.properties", ignoreResourceNotFound = true) })

public class AppConfig {
    
    @Value("${name:}")
    private String name;
    
    // 用於處理編碼問題

    @Bean
    public Filter characterEncodingFilter() {
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceEncoding(true);
        return characterEncodingFilter;
    }

    //HikariDataSourceConfiguration 有這種神奇的東西再來研究一下

    @Bean(destroyMethod = "close")
    public DataSource dataSource(){
        
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName("org.postgresql.Driver");
        hikariConfig.setJdbcUrl("jdbc:postgresql://127.0.0.1:5432/test?charset=UTF8"); 
        hikariConfig.setUsername("root");
        hikariConfig.setPassword("abcdEF123456");

        hikariConfig.setPoolName("springHikariCP");
        //實驗結果是要把它關掉,不然事務控制會無效

        hikariConfig.setAutoCommit(false);
        
        hikariConfig.addDataSourceProperty("cachePrepStmts", "true");
        hikariConfig.addDataSourceProperty("prepStmtCacheSize", "250");
        hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
        hikariConfig.addDataSourceProperty("useServerPrepStmts", "true");
          
        hikariConfig.setMinimumIdle(20);
        hikariConfig.setMaximumPoolSize(20);
        hikariConfig.setConnectionInitSql("SELECT 1");
        
        HikariDataSource dataSource = new HikariDataSource(hikariConfig);
        return dataSource;
    }
    
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        return sessionFactory.getObject();
    }
}

這邊 @MapperScan 是 mybatis 需要配置的,不然你沒辦法用ORM

再來 hikariConfig.setAutoCommit(false); 記得一定要關
因為這貨 autoCommit (default : true) 預設是開啊.....我需要事務回滾你Commit掉了我不知道怎麼回滾耶
JDBC Connection Pool [HikariCP] 設定 - 世界の一部

Controller

AccountController.java
package com.sam.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.sam.modules.entity.Userinfo;
import com.sam.modules.service.UserinfoService;

@RestController
@RequestMapping(value = "/api")
public class AccountController {
    
    @Autowired
    private UserinfoService userinfoService;
    
    @RequestMapping(value = "/userinfo", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE )
    public ResponseEntity save(@RequestBody final Userinfo userinfo){
        ResponseEntity response = null;
        try {
            userinfoService.save(userinfo);
            response = new ResponseEntity<Void>(HttpStatus.OK);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return response;
    }
    
    
    @RequestMapping(value = "/userinfo/{id}", method = RequestMethod.GET )
    public ResponseEntity get(@PathVariable("id") Long id) {
        ResponseEntity response = null;
        Userinfo userinfo = userinfoService.get(id);
        response = new ResponseEntity<Userinfo>(userinfo, HttpStatus.OK);
        return response;
    }
    
    @RequestMapping(value = "/userinfo/{id}", method = RequestMethod.DELETE )
    @ResponseStatus(HttpStatus.OK)
    public void delete(@PathVariable("id") Long id) {
        userinfoService.delete(id);
    }
}

應該很好理解,也試了幾種回覆的寫法,看自己喜好寫吧

Service

UserinfoService.java
package com.sam.modules.service;

import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.sam.modules.dao.UserinfoMapper;
import com.sam.modules.entity.Userinfo;

@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class UserinfoService extends BaseService<Userinfo, Long>{

    @Autowired
    private UserinfoMapper userinfoMapper;

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
    public int save(Userinfo record) {
        record.setTimecreate(new Date(System.currentTimeMillis()));
        return userinfoMapper.insert(record);
    }
    
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
    public int updateSelective(Userinfo record) {
        return userinfoMapper.updateByPrimaryKeySelective(record);
    }
    
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
    public int updateByReplace(Userinfo record) {
        return userinfoMapper.updateByPrimaryKey(record);
    }

    public Userinfo get(Long serno) {
        return userinfoMapper.selectByPrimaryKey(serno);
    }
    
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
    public int delete(Long serno) {
        return userinfoMapper.deleteByPrimaryKey(serno);
    }
}

這邊就需要對每個方法設定Transactional控制方式,也還好就兩種不要複製錯了就好

打包

在Gradle中增加了 spring-boot-gradle-plugin
所以你只要執行 assemble 就會把所有 Library 包含你的程式打包成一個 Jar 檔
p.s.這邊不能自己用Gradle zip打包,會得到spring boot 1.0版= =.....

執行

在eclipse中是執行 Application.java

命令方式

java -jar springboot-all-1.0.jar

參數檔預設會尋找 application.yaml 或是 application.properties
在程式中就可以透過 @Value("${version}") 來取得變數

這邊設定了多個profiles為了對應不同環境
透過命令列執行可以指定運行哪個設定

java -jar springboot-all-1.0.jar --spring.profiles.active=production
java -jar springboot-all-1.0.jar --spring.profiles.active=docker --dbip=192.168.0.1
application.yaml
version: v0.1.0

server:
    port: 9001

spring:
  profiles.active: dev
---
spring:
  application.name: NtAuth
  profiles: dev

datasource:
  url: jdbc:postgresql://127.0.0.1:5432/nt?charset=UTF8
  username: root
  password: abcdEF123456
  driverClassName: org.postgresql.Driver

---
spring:
   profiles: production

datasource:
  url: jdbc:mysql://production.com:3306/productdb
  username: db_user
  password: db_password
  driverClassName: com.mysql.jdbc.Driver
---

spring:
   profiles: docker

datasource:
  url: jdbc:postgresql://${dbip}:5432/nt?charset=UTF8$
  username: ${datasource.username}
  password: ${datasource.password}
  driverClassName:  org.postgresql.Driver

但是 Spring Boot 預設 yaml 的參數檔較優先,如果兩個檔都存在的話,有效的會是 yaml

Spring Boot 所提供的配置優先級順序比較複雜。按照優先級從高到低的順序,具體的列表如下

1. Command line arguments.
2. JNDI attributes from java:comp/env.
3. Java System properties (System.getProperties()).
4. OS environment variables.
5. A RandomValuePropertySource that only has properties in random.*.
6. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants)
7. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants)
8. Application properties outside of your packaged jar (application.properties and YAML variants).
9. Application properties packaged inside your jar (application.properties and YAML variants).
10. @PropertySource annotations on your @Configuration classes.
11. Default properties (specified using SpringApplication.setDefaultProperties).

或是你可以在命令列指定如下

java -jar springboot-all-1.0.jar --spring.config.location=./application.properties

Spring Boot其他項目功能

名稱 說明
spring-boot-starter 核心 POM,包含自動配置支持、日誌庫和對 YAML 配置文件的支持。
spring-boot-starter-amqp 通過 spring-rabbit 支持 AMQP。
spring-boot-starter-aop 包含 spring-aop 和 AspectJ 來支持面向切麵編程(AOP)。
spring-boot-starter-batch 支持 Spring Batch,包含 HSQLDB。
spring-boot-starter-data-jpa 包含 spring-data-jpa、spring-orm 和 Hibernate 來支持 JPA。
spring-boot-starter-data-mongodb 包含 spring-data-mongodb 來支持 MongoDB。
spring-boot-starter-data-rest 通過 spring-data-rest-webmvc 支持以 REST 方式暴露 Spring Data 倉庫。
spring-boot-starter-jdbc 支持使用 JDBC 訪問數據庫。
spring-boot-starter-security 包含 spring-security。
spring-boot-starter-test 包含常用的測試所需的依賴,如 JUnit、Hamcrest、Mockito 和 spring-test 等。
spring-boot-starter-velocity 支持使用 Velocity 作為模板引擎。
spring-boot-starter-web 支持 Web 應用開發,包含 Tomcat 和 spring-mvc。
spring-boot-starter-websocket 支持使用 Tomcat 開發 WebSocket 應用。
spring-boot-starter-ws 支持 Spring Web Services。
spring-boot-starter-actuator 添加適用於生產環境的功能,如性能指標和監測等功能。
spring-boot-starter-remote-shell 添加遠程 SSH 支持。
spring-boot-starter-jetty 使用 Jetty 而不是默認的 Tomcat 作為應用服務器。
spring-boot-starter-log4j 添加 Log4j 的支持。
spring-boot-starter-logging 使用 Spring Boot 默認的日誌框架 Logback。
spring-boot-starter-tomcat 使用 Spring Boot 默認的 Tomcat 作為應用服務器。

完整程式碼GitHub

參考資料
使用Gradle打包SpringBoot
設定檔的說明
簡化你的命令列變數長度

← ElasticSearch安裝中文分詞 Collecting Log Data from Windows Use Nxlog Fluentd to Elasticsearch →
 
comments powered by Disqus