Spring boot 學習筆記 – 使用 Kotlin 做後端開發

學了這麼厲害的 Kotlin 可不可以拿它來開發後端(Backend),答案是當然可以的!
我們使用 Kotlin 跟 Gradle 來做開發
先做一個非常簡單的 API 再來慢慢進階

前端?後端?前台?後台?有差嗎?

在這邊簡單解釋一下 Web 技術這些詞彙的定義,大家也常常搞錯。

  • 前端 (Frontend):由後端 (Backend) 收到資料,透過瀏覽器渲染出來跟使用者互動的介面 (UI) 與程式都稱前端。

  • 後端 (Backend):也就是伺服器 (Server) 的部分,專門處理由使用者的要求,提供對應資料的程式。

  • 前台:大家比較通俗的講法,使用者看得到的操作得到的區域,如同舞台一般。

  • 後台:大家比較通俗的講法,通常是指管理者介面,如同舞台的後台。

那前台、後台跟前端、後端有關係嗎?
答案是:沒有什麼關係。
前台需要有前端(介面),也需要後端(伺服器程式)幫忙,後台也是。

工作上可以細分成:

  • 前台的前端
  • 前台的後端
  • 後台的前端
  • 後台的後端

分別有前端工程師與後端工程師負責開發。
今天的主題是後端開發的主題,所以沒有介面只有資料。

學習目標

  • 手把手製作一個 REST API
  • 熟悉 Spring boot 的 Annotation 寫法
  • 調整 404 頁面

準備開發環境

這邊有三種做法,好壞都不一

  • 直接推薦:InteliJ IDEA Ultimate 付費版
  • Visual studio code 外掛 Spring Initializr plug-ins
  • Spring Tools 4 for Eclipse (前身為 STS3)

💡 小提醒:這個需要付費版 InteliJ IDEA Ultimate, 免費的 Community 會無法正確 Compile,小弟已經親身試過了。

小弟建議使用 InteliJ,因為他的優異程式碼提示、搜尋與重構等功能好過其他的 IDE 編輯器

小提醒:因為 InteliJ IDEA 有可能會小改版,介面可能會稍微長的不太一樣,如果真有找不到選項、或者文章失效歡迎聯繫小弟我。

開新專案

新增SpringBoot專案

New Project,選擇 Spring Initializr

  • Group: <自己填>
  • Artifact: <自己填>
  • Type: Gradle Project (Generate a Gradle based project archive.)
  • Language: Kotlin
  • Packaging: Jar
  • Java Version: 11 (選更新的版本也可以)
  • Version: <自己填>
  • Name: <自己填>
  • Description: <自己填>
  • Package: <自己填>

然後按「Next」。

新增SpringBoot專案

這邊選擇二個:

  • Web > Spring Web
  • Developer Tools > Spring Boot DevTools

不選也沒關係,後面可以自行新增。
按下「Finish」完成建立專案。

如果直接新增完跑的話,Log 大概會長這樣。

瀏覽會長這樣:

資料夾結構

如果都是預設值的話,會看到以下資料夾結構:

  • src/main/kotlin/\<packageName>/
    主要程式碼的目錄
  • src/test/kotlin/\<packageName>/
    測試程式碼的目錄
  • build.gradle.kts
    專案的 gradle 檔案
  • src/main/resources/static
    存放一些靜態資源(例如:JavaScript、css、圖片…等)
  • src/main/resources/templates
    存放一些樣板檔案
  • src/main/resources/application.properties
    參數設定配置檔,存放一些資料連線資訊、雜項設定等等

修改啟動的連接埠 (Port)

找到 application.properties 檔案,加入這句即可

server.port=8086

啟動 Port 就改成 8086 了

其他參數請見:
https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html

增加 Dependencies

打開 build.gradle.kts 找到 dependencies { } 的區塊

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    developmentOnly("org.springframework.boot:spring-boot-devtools")
}

即是 Spring Web 跟 Spring Boot DevTools


觀察檔案

DemoApplication.kt (如果沒改名字的話)
或者有 @SpringBootApplication 標明字樣的檔案
即主要程式,程式的進入點
可以找到一個

fun main(args: Array<String>) {
    runApplication<DemoApplication>(*args)
}

可以找到 main 即程式進入點


寫第一個 Controller 寫一隻 API

著手來寫第一個 Controller

定義資料 Model

我們先定義幾個的 model class,

GeneralMessageResponse 是用來顯示範例 API 資料用的

GeneralMessageResponse.kt

package com.example.demo.model

data class GeneralMessageResponse(val message:String)

就這樣二句,就可以定義好資料 Model。

GeneralErrorResponse 用來顯示錯誤訊息的

GeneralErrorResponse.kt

package com.example.demo.model

data class GeneralErrorResponse(var message:String)

寫一個 Controller

製作一個 class 名叫 HelloController 使用 @RestController 標示,內容如下:

HelloController.kt

package com.example.demo

import com.example.demo.model.GeneralMessageResponse
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class HelloController{

    @RequestMapping("/")
    fun hello(): GeneralMessageResponse {
        return GeneralMessageResponse("Hello, world!")
    }
}

做一個方法 (Method),方法名稱可以自訂,標上 @RequestMapping 標示,回傳一個物件
即回傳的資料

@RequestMapping 上面需標明綁定的 API 路徑(不可重複)
可綁定何種傳送方式 (GET, POST, PUT, DELETE)

可編譯此專案,並瀏覽之
預設會是 http://localhost:8080/

會得到以下結果:

{"message":"Hello, world!"}

新增另外一個 Method

在剛剛的 HelloController 在新增一個方法 (Method),名叫 getHello()
程式碼片段如下:

@RequestMapping("/hello", method = [RequestMethod.GET])
fun getHello(@RequestParam("name", defaultValue = "World") name: String): GeneralMessageResponse {
    return GeneralMessageResponse("Hello, $name")
}

比起剛剛的 Method 多了參數,只接受 GET 方法(使用其他方式存取會錯誤,因為沒定義)
不標明等於接收所有的方式

參數部分使用 @RequestParam 修飾字標明,標上參數名稱,
defaultValue 如果沒填資料的話的預設值為何
然後像剛才一樣,回傳一個 JSON 物件

方式+名稱 的組合不可重複,但可接受同名但不同的方式的 Method
例如這樣是合法的:

@RequestMapping("/hello")
fun hello(): GeneralMessageResponse {
    return GeneralMessageResponse("Hello, world (from default method)!")
}

@RequestMapping("/hello", method = [RequestMethod.GET])
fun getHello(@RequestParam("name", defaultValue = "World") name: String): GeneralMessageResponse {
    return GeneralMessageResponse("Hello, $name (from get)")
}

@RequestMapping("/hello", method = [RequestMethod.POST])
fun postHello(@RequestParam("name", defaultValue = "World") name: String): GeneralMessageResponse {
    return GeneralMessageResponse("Hello, $name (from post)")
}

後面 Restful API 的時候會再提到。


錯誤處理

錯誤處理有二個部分,不太一樣:

  • 執行時出現 Exception 時,可自訂回傳的錯誤訊息
  • 當存取一個不存在的 API 時,預設回的訊息( 404 Not found 頁面)

處理 Exception 錯誤

先新增一個 GeneralErrorResponse 做為錯誤訊息的回應

GeneralErrorResponse.kt

package com.example.demo.model

import com.fasterxml.jackson.annotation.JsonInclude

@JsonInclude(JsonInclude.Include.NON_NULL)
data class GeneralErrorResponse(
    val message:String, 
    val requestUrl: String? = null, 
    val errorStackStace: String? = null
)

新增一個 ThrowableExtension.kt 新增一個 stackTraceAsString() 拿到 stackTrace 的字串

ThrowableExtension.kt

package com.example.demo.utils

import java.io.PrintWriter
import java.io.StringWriter

fun Throwable.stackTraceAsString(): String {
    val errors = StringWriter()
    this.printStackTrace(PrintWriter(errors))
    return errors.toString()
}

最後新增 GlobalExceptionHandler 用來處理全域的 API 錯誤問題

GlobalExceptionHandler.kt

package com.example.demo

import com.example.demo.model.GeneralErrorResponse
import com.example.demo.utils.stackTraceAsString
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.bind.annotation.ResponseStatus
import javax.servlet.http.HttpServletRequest

@ControllerAdvice
class GlobalExceptionHandler {
    @ExceptionHandler(Throwable::class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    @ResponseBody
    fun handleException(request: HttpServletRequest, error: Exception): GeneralErrorResponse {
        return GeneralErrorResponse("Error occurred!", request.requestURL.toString(), error.stackTraceAsString())
    }
}

這邊使用了 @ControllerAdvice@ExceptionHandler 標示
當然不同情境有不同的用法,這邊示範回傳一個 JSON

最後在 Controller 新增一個測試用的 Exception 方法

@RequestMapping("/testErr")
fun testErr() {
    throw Exception("test exception")
}

然後瀏覽測試之。

處理 404 Not found 頁面

這裡處理當存取一個不存在的 API 時,預設回的資料。
我們再新增一個 Controller 名叫 MyErrorController 實作 ErrorController 介面,
範例如下:

MyErrorController.kt

package com.example.demo

import com.example.demo.model.GeneralErrorResponse
import org.springframework.boot.web.servlet.error.ErrorController
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class MyErrorController : ErrorController {
    override fun getErrorPath(): String {
        return "/error"
    }

    @RequestMapping("/error")
    fun handleError(): GeneralErrorResponse {
        return GeneralErrorResponse("404 Error")
    }
}

這裡需實作 ErrorController 介面,定義錯誤發生的預設 Path 將走去哪裡
這裡使用 "/error"
然後綁定前述的值,回應一個預設訊息。


參考資料

[JSP] JSP連接MySQL初探

這篇算是因為網友詢問,才會有的一篇文章吧

 

最近也在看JSP的一些寫法

因為還沒有看很懂,所以這一篇一直都沒有寫

用Java跑網頁有二種型態

  • Servlet
  • JSP

Servlet可以算是JSP的前身,可以處理比較複雜的事情
JSP的寫法比較像PHP,那我們就從這裡開始吧

範例不難,但環境設定上頗麻煩的

——————————————————–

環境設定

再開始之前,先講一下環境設定

標題是連MySQL資料庫嘛,所以要有MySQL取得方式我就不細講了

有在寫PHP+MySQL的話,目前在使用的MySQL就大方的借過來用吧

 

 

若是使用整合環境(如appserv、wamp、xampp…等等)

就只需要補上JDK、Tomcat、MySQL Connecter,官方下載在這裡:

JDK

http://www.oracle.com/technetwork/java/javase/downloads/index.html

Tomcat

http://tomcat.apache.org/download-70.cgi

MySQL Connecter for Java

http://www.mysql.com/downloads/connector/j/

選擇你的作業系統看是x86還是x64,選擇對應的檔案安裝

然後用Next大法(一直Next),一路給他裝完

 

如果,你像我一樣覺得這樣裝很凌亂,或是說是新建環境,甚麼都沒有的話

可以採用新版的XAMPP 1.81

http://www.apachefriends.org/zh_tw/xampp.html

這個版本裡面就有附tomcat摟

少一個要補檔的對象

接下來只要下載JDK + MySQL Connecter就行摟!

 

 

個人是建議用JDK+XAMPP的配置,或是整合環境+JDK+官方tomcat

(對了,新版含tomcat的XAMPP不能跟tomcat混裝,路徑會錯亂…)

XAMPP他主要功能有包入Apache HTTP Server、MySQL Server、FileZilla FTP Server、Mail Server、Tomcat Server

細項有php、pear、perl、cgi、phpmyadmin、mbstring、gd….等等(糟糕離題了…)

 

反正,整合環境有他的好處,除了設定檔要自己設定之外

其他大多都設定好了

 

 

MySQL Connector的地方先說明一下

只要下載完畢,解壓縮

取出mysql-connector-java-5.1.22-bin.jar這個檔案就好,壓縮檔裡的其他資料可以不用

(筆者抓到的是Connector/J 5.1.22版本,所以檔案叫做mysql-connector-java-5.1.22-bin.jar

請依照你下載的版本做變化)

然後將它放到tomcat的目錄的lib裡,所以變成

官方Tomcat路徑:

C:\Program Files\Apache Software Foundation\Tomcat 7.0\lib\mysql-connector-java-5.1.22-bin.jar

XAMPP包含tomcat路徑:

C:\xampp\tomcat\lib\mysql-connector-java-5.1.22-bin.jar

 

環境變數

主要有點麻煩的是Java的環境設定

Windows環境變數的方法在這裡:

WinXP在我的電腦按滑鼠右鍵 -> 內容 -> (上方頁籤)進階 -> 環境變數

Win7是在開始 -> 我的電腦按右鍵 -> 內容 -> (在左側欄)進階系統設定 -> (上方頁籤)進階 -> 環境變數

 

要添加這些環境變數

純JAVA環境
———————
JAVA_HOME
C:\Program Files\Java\jdk1.6.0_35
CLASSPATH
.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\jre;%JAVA_HOME%\src
Path
.;%JAVA_HOME%\bin

JAVA環境+Tomcat網頁伺服器環境
———————
JAVA_HOME
C:\Program Files\Java\jdk1.6.0_35
CATALINA_HOME
(官方Tomcat路徑)
C:\Program Files\Apache Software Foundation\Tomcat 7.0
 
(XAMPP包含tomcat路徑)
C:\xampp\tomcat
(二者依環境,擇其一使用) 

CLASSPATH
.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\jre;%JAVA_HOME%\src;%CATALINA_HOME%\lib\servlet-api.jar;%CATALINA_HOME%\lib\mysql-connector-java-5.1.22-bin.jar
Path
.;%JAVA_HOME%\bin

有些地方要注意

如果裡面有值的話(像是Path),打個分號 ; 再連接上去

藍字的地方是他的版本號,要依照你的版本做變化(查看一下該路徑是否有東西)

設定完畢要重新開機(使用者登出再登入)才會生效

這樣就設定完成了

 

弄到這裡可以先測試一下

phpmyadmin是否能正常連線MySQL?(代表php+MySQL正常)

tomcat是否正常啟動? http://localhost:8080/ 有沒有東西?

 

—————————————————————————-

建立範例DB

終於回到程式面了,可以開個資料庫名字叫test_jspdb

執行以下SQL;

CREATE TABLE IF NOT EXISTS `employee` 
`emp_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`emp_name` varchar(100) NOT NULL,
`emp_age` int(11) NOT NULL,
PRIMARY KEY (`emp_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3;

INSERT INTO `employee` (`emp_id`, `emp_name`, `emp_age`) VALUES
(1, ‘mary’, 20)
(2, ‘amy’, 22);

 

資料表內容很簡單,就只有員工姓名,員工年齡而已

—————————————————————-

 

JSP範例

這時候可以建立一個連線的範例來做了

網站的根目錄在:

C:\xampp\tomcat\webapps\ROOT

C:\Program Files\Apache Software Foundation\Tomcat 7.0\webapps\ROOT

在那裡建立一個空檔案db_test.jsp然後貼上以下程式碼。

然後瀏覽 http://localhost:8080/db_test.jsp 看看能不能使用

如果出現can’t load mysql driver代表說MySQL Connector的設定有問題

查看檔案位置是否正確及環境變數設定是否有更新

 

 

 

<%@ page contentType=”text/html;charset=utf-8″%>

<%@ page import=”java.sql.*”%>
<%
try
{
//讀取mysqlDriver驅動程式
Class.forName(“org.gjt.mm.mysql.Driver”).newInstance();

try
{
//連接mysql資料庫

// 資料庫名稱”zend_test”,帳號”root”,密碼”123456″,
// 使用Unicode編碼”true”,字元集”UTF-8″
String db_user=”root”;
String db_pwd=”12345″;
String db_database=”test_jspdb”;
Connection conn= DriverManager.getConnection(“jdbc:mysql://localhost:3306/”+db_database+”?user=”+db_user+”&password=”+db_pwd+”&useUnicode=true&characterEncoding=UTF-8″);

try
{
//建立statement
Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
try
{
//建立SQL查詢
String sql=”select * from album”;
ResultSet rs = stmt.executeQuery(sql);

%>

<h2>Employee</h2>
<table border=1>
<tr><th>id</th><th>artist</th><th>title</th></tr>
<%
//顯示資料

while(rs.next())
{
%>
<tr>
<td><%=rs.getString(“id”)%></td><td><%=rs.getString(“artist”)%></td><td><%=rs.getString(“title”)%></td>
</tr>
<%
}
%>
</table>
<%

// 關閉連線
rs.close();
rs = null;
stmt.close();
stmt = null;
conn.close();

}
catch (Exception ex)
{
out.println(“can’t read data”);
out.println(ex.toString());
}
}
catch (Exception e)
{
out.println(“can’t create statement”);
out.println(e.toString());
}
}
catch(Exception e)
{
out.println(“can’t content mysql database”);
out.println(e.toString());
}

}
catch(Exception e)
{
out.println(“can’t load mysql driver”);
out.println(e.toString());
}

%>

 

 寫法跟PHP差不多吧!有個重點要提一下

 

其餘的update insert delete指令可以這樣寫)

String sql = “INSERT INTO `employee` (`emp_name`, `emp_age`) VALUES
(‘mary’, 20),”;

// 執行SQL語法
Statement stmt = conn.createStatement();
stmt.executeUpdate(sql);

 

你可能會疑惑createStatement這個方法

ResultSet.TYPE_FORWARD_ONLY (預設值)

就只能使用next()方法取得下一筆(指標順序定位)

 

ResultSet.TYPE_SCROLL_INSENSITIVE與 ResultSet.TYPE_SCROLL_SENSITIVE

能使用afterLast()、previous()、absolute()、relative()其他方法

有 ResultSet.CONCUR_READ_ONLY與ResultSet.CONCUR_UPDATABLE兩個參數可以設定,前者表示只能讀取 ResultSet的資料,後者表示可以直接使用ResultSet來操作資料庫

(這個部份還沒有驗證,我想應該跟update insert delete指令無關)

 

如果有誤煩請指正

 

參考資料

http://caterpillar.onlyfun.net/Gossip/JavaGossip-V2/ResultSetCursor.htm

http://jjnnykimo.pixnet.net/blog/post/21585335-jsp%E9%80%A3%E7%B5%90mysql%E8%B3%87%E6%96%99%E5%BA%AB%E7%AF%84%E4%BE%8B

http://blog.yam.com/carl44/article/14825336

http://blog.alumni.nctu.edu.tw/plate/web/papermsg.jsp?UI=markcool&PI=96