本次的練功場重點:
(1)複習上次的Basic-3-Layer
(2)練習Update跟Delete資料
(3)IOC範例介紹
https://blog.csdn.net/UmGsoil/article/details/74987807
(4)稍微提一下TDD測試
第一部分就略過,有來練功場的就能再溫習一下上次教得進度!這次就延續前一堂學過的,本來是只有做到進行查詢跟新增使用者資料,這次補齊了修改Update跟刪除Delete資料的動作。
練習Update跟Delete資料
service
/service/UserService.kt
照慣例,先把實作的interface寫進來,分別是modifyUser跟removeById
fun modifyUser(userDto: UserDto): UserDto
fun removeById(id: Long)
/service/impl/RealUserService.kt
(1)fun modifyUser 的動作:將呼叫傳入的使用者資料userDto:UserDto,透過id用finById找出資料庫裡頭的資料User,再將此查到的User的name跟age塞入userDto.name跟userDto.age,最後再用userpository.save(this)進行資料庫的更新。
- 這裡特別注意: JPA的方式沒有insert/update區分,一律都是用save,所以在update之前要先將資料query出來,取得id之後再寫回資料庫去更新。如果你沒有先給User的id,直接使用save反而會變成再新增一筆User資料了。
(2)fun removeById 的動作:將想要刪除的使用者id,透過UserRepository的deleteById方法,把資料刪掉
override fun modifyUser(userDto: UserDto) =
userRepository.findById(userDto.id).orElseThrow {
RuntimeException()
}.apply {
name = userDto.name
age = userDto.age
userRepository.save(this)
}.run {
UserDto(this.id!!, "${name}", this.age)
}
override fun removeById(id: Long) = userRepository.deleteById(id)
controller
/controller/UserApi.kt
一樣先加interface, modifyUser跟removeById
(1) 變更使用者 fun modifyUser
@PutMapping(“/user”) 表示http request用Put方式進行
(@RequestBody userDto:UserDto ) 表示將以UserDto的資料格式傳入
(2)刪除使用者fun removeById
@DeleteMapping(“/user”) 表示http request用Delete方式進行
(@RequestParm id:Long):UserDto 表示用id傳入參數
interface UserApi{
@PutMapping("/user")
fun modifyUser(@RequestBody userDto: UserDto): UserDto
@DeleteMapping("/user")
fun removeById(@RequestParam id: Long)
}
/controller/impl/UserController.kt
(1) fun modifyUser 的動作:將傳入的使用者資料UserDto,傳遞給UserService,接著再從UserService傳遞給UserRepository達到更新使用者的功能。
(2)fun removeById 的動作:將傳入的查詢使用者id,接著再從UserService傳遞給UserRepository達到刪除使用者的功能
@RestController
class UserController(@Qualifier("RealUserService") val userService: UserService) : UserApi {
override fun modifyUser(userDto: UserDto) = userService.modifyUser(userDto)
override fun removeById(id: Long) = userService.removeById(id)
}
測試執行
一樣使用上次產生的UserRequest.http檔案,更新的http request的寫法如下,我們要把在資料庫的id=1的User資料更新為下列
PUT http://localhost:8080/user
Content-Type: application/json
{
"id": 1,
"name": "update demo",
"age": 777
}
按下綠色執行箭頭,開始執行更新
跑出結果回傳200 OK了!
刪除的寫法如下,我們要刪除id=2的User
DELETE http://localhost:8080/user?id=2
按下綠色執行箭頭,開始執行刪除
跑出結果回傳200 OK,回頭看資料庫,也只剩剛剛更新的一筆資料了!
IOC介紹
這次解說怎麼將我們學到的Basic-3-Layer用applicationContext.xml跟annotation來做到IOC原則。
據說以前都幾乎是用xml去寫這些設定,但是自從Spring一直不斷地演進(黑魔法),現在不太用xml去寫,用annotation一樣能做到一樣的效果。
首先我們先下載這次練功場的sample code:
假設我們沒有利用application context.xml或是使用annotation建立對應依賴的的bean,在Controller呼叫findName將不會成功,因為沒有透過IOC來幫忙把相依的Service幫我們建立,而且程式也沒指定要使用的Sevice,所以會出錯。而Service2Lower跟Service2Uper也一樣,有相依Repository,所以呼叫findName也會出錯。
applicationContext.xml設定
所以我們回頭看applicationContext.xml裡頭的設定
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<!-- Scan for spring annotated components -->
<context:component-scan base-package="org.yfr"/>
<!-- Process annotations on registered beans like @Autowired... -->
<context:annotation-config/>
<bean id="repositoryQ" class="org.yfr.RepositoryQ"/>
<bean id="repositoryV" class="org.yfr.RepositoryV"/>
<bean id="service2Lower" class="org.yfr.Service2Lower">
<property name="repository" ref="repositoryQ"/>
</bean>
<bean id="service2Upper" class="org.yfr.Service2Upper">
<property name="repository" ref="repositoryV"/>
</bean>
<bean id="controller" class="org.yfr.Controller">
<property name="service" ref="service2Lower"/>
</bean>
</beans>
透過xml檔案的設定來建立RepositoryQ 跟RepositoryV的class
<bean id="repositoryQ" class="org.yfr.RepositoryQ"/>
<bean id="repositoryV" class="org.yfr.RepositoryV"/>
再來是實作的Service2Lower跟service2Upper class我們也要設定起來,而且要設定property name=repository,把裡頭會相依到的repository進行reference,Service2Lower我們會參考repositoryQ而service2Upper會參考repositoryV。
到了最上層的Controller要呼叫service取用功能時,也要設定bean來建立class,然後property name=service,要用的service可以是service2Lower或是service2Upper都行。
<bean id="controller" class="org.yfr.Controller">
<property name="service" ref="service2Lower"/>
</bean>
如果是用service2Lower,執行TestMain時就會把RepositoryQ的findName內容用小寫印出” findname test repositoryq”
試著把controller指定給service2Upper
<bean id="controller" class="org.yfr.Controller">
<pr
operty name="service" ref="service2Upper"/>
</bean>
同理,執行TestMain時就會把RepositoryV的findName內容用大寫印出” FINDNAME TEST REPOSITORY”
這樣的話,改xml設定,就能很快替換直接實作不同的方法了,我想這就是IOC帶來的好處吧,都把new跟依賴的動作都交給框架處理,我們只要專注在實現功能面。
Annotation設定
接下來換用annotation來練習,先把Controller.java 加上@org.springframework.stereotype.Controller跟幫Service加上@Autowired
@org.springframework.stereotype.Controller
public class Controller {
@Autowired
private Service service;
public String findName() {
return service.findName();
}
}
RepositoryQ.java加上@org.springframework.stereotype.Repository
@org.springframework.stereotype.Repository
public class RepositoryQ implements Repository {
@Override
public String findName() {
return "findName Test RepositoryQ";
}
}
RepositoryV.java加上@org.springframework.stereotype.Repository
@org.springframework.stereotype.Repository
public class RepositoryV implements Repository {
@Override
public String findName() {
return "findName Test RepositoryV";
}
}
我們先讓Controller使用Service2Upper讓測試的字印出大寫,所以先在Service2Upper.java加上@Component,然後將原本的命名的repository改成repositoryV,會自動幫我們使用repossitoryV去將findName內容印出(我試過改成其他命名方式就會出錯,看來真的是黑魔法幫我們去找到respositoryV了)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Service2Upper implements Service {
@Autowired
private Repository repositoryV;
@Override
public String findName() {
return repositoryV.findName().toUpperCase();
}
}
執行TestMain大寫印出” FINDNAME TEST REPOSITORY
如果要換成小寫印出,就把Service2Upper.java的@Component拿掉,換放在Service2Lower.java
@Component
public class Service2Lower implements Service {
@Autowired
private Repository repositoryQ;
@Override
public String findName() {
return repositoryQ.findName().toLowerCase();
}
}
執行TestMain時小寫印出” findname test repositoryq”
眼尖的就會看到console Log,Spring幫我們定義了controller, repositoryQ, respositryV跟service2Lower
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@55ca8de8: defining beans [controller,repositoryQ,repositoryV,service2Lower,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
自己練習下來的感想是,感覺好像寫xml比較快,因為只要設定一個檔案,不用在每個檔案都要寫annotation(難道我是古時代還沒進化嗎…? 或是code寫不夠多!?),結果詢問講者大大,真的是我Code寫不過多,因為測試的sample規模太小,如果專案變大,加上大家的xml的style都不一樣,可能又會變成難維護的災難了,所以才有annotaion,據說之後黑魔法又要把annotation消滅,就看Spring怎麼幫我們進化了XD。
接下來的測試部分從缺…
測試的部份因為沒寫過,我還需要花一些時間來理解,自己練習過才知道怎麽寫,有興趣的可以去講者大大提供的Unit Test範例先練習
https://github.com/b2etw/Spring-Boot-Kotlin-Dojo/tree/master/stage3/demo-unit-test
而且下一次也會再介紹地更深入,就先趁這時間先學TDD一下,會隨時有空更新。
以上就是練功場的第三堂體驗,學會IOC的概念,還有帶領進入測試的世界(只是我還沒踏進去…還在新手村練習)。
歡迎各位也一起來練功場學習Spring囉!
2020–08–2~2020–09–27相約在線上! 隔週一起來練功!