一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|JavaScript|易語言|

服務(wù)器之家 - 編程語言 - JAVA教程 - 通過Spring Boot + Mybatis + Redis快速搭建現(xiàn)代化Web項目

通過Spring Boot + Mybatis + Redis快速搭建現(xiàn)代化Web項目

2021-02-27 14:26roncoo JAVA教程

本篇文章介紹了如何通過Spring Boot、Mybatis以及Redis快速搭建一個現(xiàn)代化的Web項目,并且同時介紹了如何在Spring Boot下優(yōu)雅地書寫單元測試來保證我們的代碼質(zhì)量。具體內(nèi)容詳情大家通過本文學(xué)習(xí)下吧

背景

springboot因其提供了各種開箱即用的插件,使得它成為了當(dāng)今最為主流的java web開發(fā)框架之一。mybatis是一個十分輕量好用的orm框架。redis是當(dāng)今十分主流的分布式key-value型數(shù)據(jù)庫,在web開發(fā)中,我們常用它來緩存數(shù)據(jù)庫的查詢結(jié)果。

本篇博客將介紹如何使用springboot快速搭建一個web應(yīng)用,并且采用mybatis作為我們的orm框架。為了提升性能,我們將redis作為mybatis的二級緩存。為了測試我們的代碼,我們編寫了單元測試,并且用h2內(nèi)存數(shù)據(jù)庫來生成我們的測試數(shù)據(jù)。通過該項目,我們希望讀者可以快速掌握現(xiàn)代化java web開發(fā)的技巧以及最佳實踐。

本文的示例代碼可在github中下載:https://github.com/lovelcp/spring-boot-mybatis-with-redis/tree/master

環(huán)境

開發(fā)環(huán)境:mac 10.11
ide:intellij 2017.1
jdk:1.8
spring-boot:1.5.3.release
redis:3.2.9
mysql:5.7

spring-boot

新建項目

首先,我們需要初始化我們的spring-boot工程。通過intellij的spring initializer,新建一個spring-boot工程變得十分簡單。首先我們在intellij中選擇new一個project:

通過Spring Boot + Mybatis + Redis快速搭建現(xiàn)代化Web項目

然后在選擇依賴的界面,勾選web、mybatis、redis、mysql、h2:

通過Spring Boot + Mybatis + Redis快速搭建現(xiàn)代化Web項目

新建工程成功之后,我們可以看到項目的初始結(jié)構(gòu)如下圖所示:

通過Spring Boot + Mybatis + Redis快速搭建現(xiàn)代化Web項目

spring initializer已經(jīng)幫我們自動生成了一個啟動類——springbootmybatiswithredisapplication。該類的代碼十分簡單:

?
1
2
3
4
5
6
@springbootapplication
public class springbootmybatiswithredisapplication {
 public static void main(string[] args) {
 springapplication.run(springbootmybatiswithredisapplication.class, args);
 }
}

@springbootapplication注解表示啟用spring boot的自動配置特性。好了,至此我們的項目骨架已經(jīng)搭建成功,感興趣的讀者可以通過intellij啟動看看效果。

新建api接口

接下來,我們要編寫web api。假設(shè)我們的web工程負(fù)責(zé)處理商家的產(chǎn)品(product)。我們需要提供根據(jù)product id返回product信息的get接口和更新product信息的put接口。首先我們定義product類,該類包括產(chǎn)品id,產(chǎn)品名稱name以及價格price:

?
1
2
3
4
5
6
7
public class product implements serializable {
 private static final long serialversionuid = 1435515995276255188l;
 private long id;
 private string name;
 private long price;
 // getters setters
}

然后我們需要定義controller類。由于spring boot內(nèi)部使用spring mvc作為它的web組件,所以我們可以通過注解的方式快速開發(fā)我們的接口類:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@restcontroller
@requestmapping("/product")
public class productcontroller {
 @getmapping("/{id}")
 public product getproductinfo(
   @pathvariable("id")
     long productid) {
  // todo
  return null;
 }
 @putmapping("/{id}")
 public product updateproductinfo(
   @pathvariable("id")
     long productid,
   @requestbody
     product newproduct) {
  // todo
  return null;
 }
}

我們簡單介紹一下上述代碼中所用到的注解的作用:

@restcontroller:表示該類為controller,并且提供rest接口,即所有接口的值以json格式返回。該注解其實是@controller和@responsebody的組合注解,便于我們開發(fā)rest api。

@requestmapping、@getmapping、@putmapping:表示接口的url地址。標(biāo)注在類上的@requestmapping注解表示該類下的所有接口的url都以/product開頭。@getmapping表示這是一個get http接口,@putmapping表示這是一個put http接口。

@pathvariable、@requestbody:表示參數(shù)的映射關(guān)系。假設(shè)有個get請求訪問的是/product/123,那么該請求會由getproductinfo方法處理,其中url里的123會被映射到productid中。同理,如果是put請求的話,請求的body會被映射到newproduct對象中。

這里我們只定義了接口,實際的處理邏輯還未完成,因為product的信息都存在數(shù)據(jù)庫中。接下來我們將在項目中集成mybatis,并且與數(shù)據(jù)庫做交互。

集成mybatis

配置數(shù)據(jù)源

首先我們需要在配置文件中配置我們的數(shù)據(jù)源。我們采用mysql作為我們的數(shù)據(jù)庫。這里我們采用yaml作為我們配置文件的格式。我們在resources目錄下新建application.yml文件:

?
1
2
3
4
5
6
7
spring:
# 數(shù)據(jù)庫配置
 datasource:
 url: jdbc:mysql://{your_host}/{your_db}
 username: {your_username}
 password: {your_password}
 driver-class-name: org.gjt.mm.mysql.driver

由于spring boot擁有自動配置的特性,我們不用新建一個datasource的配置類,sping boot會自動加載配置文件并且根據(jù)配置文件的信息建立數(shù)據(jù)庫的連接池,十分便捷。

筆者推薦大家采用yaml作為配置文件的格式。xml顯得冗長,properties沒有層級結(jié)構(gòu),yaml剛好彌補了這兩者的缺點。這也是spring boot默認(rèn)就支持yaml格式的原因。

配置mybatis

我們已經(jīng)通過spring initializer在pom.xml中引入了mybatis-spring-boot-starte庫,該庫會自動幫我們初始化mybatis。首先我們在application.yml中填寫mybatis的相關(guān)配置:

?
1
2
3
4
5
6
7
# mybatis配置
mybatis:
 # 配置映射類所在包名
 type-aliases-package: com.wooyoo.learning.dao.domain
 # 配置mapper xml文件所在路徑,這里是一個數(shù)組
 mapper-locations:
 - mappers/productmapper.xml

然后,再在代碼中定義productmapper類:

?
1
2
3
4
5
6
7
@mapper
public interface productmapper {
 product select(
   @param("id")
     long id);
 void update(product product);
}

這里,只要我們加上了@mapper注解,spring boot在初始化mybatis時會自動加載該mapper類。

spring boot之所以這么流行,最大的原因是它自動配置的特性。開發(fā)者只需要關(guān)注組件的配置(比如數(shù)據(jù)庫的連接信息),而無需關(guān)心如何初始化各個組件,這使得我們可以集中精力專注于業(yè)務(wù)的實現(xiàn),簡化開發(fā)流程。

訪問數(shù)據(jù)庫

完成了mybatis的配置之后,我們就可以在我們的接口中訪問數(shù)據(jù)庫了。我們在productcontroller下通過@autowired引入mapper類,并且調(diào)用對應(yīng)的方法實現(xiàn)對product的查詢和更新操作,這里我們以查詢接口為例:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
@restcontroller
@requestmapping("/product")
public class productcontroller {
 @autowired
 private productmapper productmapper;
 @getmapping("/{id}")
 public product getproductinfo(
   @pathvariable("id")
     long productid) {
  return productmapper.select(productid);
 }
 // 避免篇幅過長,省略updateproductinfo的代碼
}

然后在你的mysql中插入幾條product的信息,就可以運行該項目看看是否能夠查詢成功了。

至此,我們已經(jīng)成功地在項目中集成了mybatis,增添了與數(shù)據(jù)庫交互的能力。但是這還不夠,一個現(xiàn)代化的web項目,肯定會上緩存加速我們的數(shù)據(jù)庫查詢。接下來,將介紹如何科學(xué)地將redis集成到mybatis的二級緩存中,實現(xiàn)數(shù)據(jù)庫查詢的自動緩存。

集成redis

配置redis

同訪問數(shù)據(jù)庫一樣,我們需要配置redis的連接信息。在application.yml文件中增加如下配置:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
spring:
 redis:
 # redis數(shù)據(jù)庫索引(默認(rèn)為0),我們使用索引為3的數(shù)據(jù)庫,避免和其他數(shù)據(jù)庫沖突
 database: 3
 # redis服務(wù)器地址(默認(rèn)為localhost)
 host: localhost
 # redis端口(默認(rèn)為6379
 port: 6379
 # redis訪問密碼(默認(rèn)為空)
 password:
 # redis連接超時時間(單位為毫秒)
 timeout: 0
 # redis連接池配置
 pool:
  # 最大可用連接數(shù)(默認(rèn)為8,負(fù)數(shù)表示無限)
  max-active: 8
  # 最大空閑連接數(shù)(默認(rèn)為8,負(fù)數(shù)表示無限)
  max-idle: 8
  # 最小空閑連接數(shù)(默認(rèn)為0,該值只有為正數(shù)才有作用)
  min-idle: 0
  # 從連接池中獲取連接最大等待時間(默認(rèn)為-1,單位為毫秒,負(fù)數(shù)表示無限)
  max-wait: -1

上述列出的都為常用配置,讀者可以通過注釋信息了解每個配置項的具體作用。由于我們在pom.xml中已經(jīng)引入了spring-boot-starter-data-redis庫,所以spring boot會幫我們自動加載redis的連接,具體的配置類
org.springframework.boot.autoconfigure.data.redis.redisautoconfiguration。通過該配置類,我們可以發(fā)現(xiàn)底層默認(rèn)使用jedis庫,并且提供了開箱即用的redistemplate和stringtemplate。

將redis作為二級緩存

mybatis的二級緩存原理本文不再贅述,讀者只要知道,mybatis的二級緩存可以自動地對數(shù)據(jù)庫的查詢做緩存,并且可以在更新數(shù)據(jù)時同時自動地更新緩存。

實現(xiàn)mybatis的二級緩存很簡單,只需要新建一個類實現(xiàn)org.apache.ibatis.cache.cache接口即可。

該接口共有以下五個方法:

string getid():mybatis緩存操作對象的標(biāo)識符。一個mapper對應(yīng)一個mybatis的緩存操作對象。

void putobject(object key, object value):將查詢結(jié)果塞入緩存。

object getobject(object key):從緩存中獲取被緩存的查詢結(jié)果。

object removeobject(object key):從緩存中刪除對應(yīng)的key、value。只有在回滾時觸發(fā)。一般我們也可以不用實現(xiàn),具體使用方式請參考:org.apache.ibatis.cache.decorators.transactionalcache。

void clear():發(fā)生更新時,清除緩存。

int getsize():可選實現(xiàn)。返回緩存的數(shù)量。

readwritelock getreadwritelock():可選實現(xiàn)。用于實現(xiàn)原子性的緩存操作。

接下來,我們新建rediscache類,實現(xiàn)cache接口:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public class rediscache implements cache {
 private static final logger logger = loggerfactory.getlogger(rediscache.class);
 private final readwritelock readwritelock = new reentrantreadwritelock();
 private final string id; // cache instance id
 private redistemplate redistemplate;
 private static final long expire_time_in_minutes = 30; // redis過期時間
 public rediscache(string id) {
  if (id == null) {
   throw new illegalargumentexception("cache instances require an id");
  }
  this.id = id;
 }
 @override
 public string getid() {
  return id;
 }
 /**
  * put query result to redis
  *
  * @param key
  * @param value
  */
 @override
 @suppresswarnings("unchecked")
 public void putobject(object key, object value) {
  redistemplate redistemplate = getredistemplate();
  valueoperations opsforvalue = redistemplate.opsforvalue();
  opsforvalue.set(key, value, expire_time_in_minutes, timeunit.minutes);
  logger.debug("put query result to redis");
 }
 /**
  * get cached query result from redis
  *
  * @param key
  * @return
  */
 @override
 public object getobject(object key) {
  redistemplate redistemplate = getredistemplate();
  valueoperations opsforvalue = redistemplate.opsforvalue();
  logger.debug("get cached query result from redis");
  return opsforvalue.get(key);
 }
 /**
  * remove cached query result from redis
  *
  * @param key
  * @return
  */
 @override
 @suppresswarnings("unchecked")
 public object removeobject(object key) {
  redistemplate redistemplate = getredistemplate();
  redistemplate.delete(key);
  logger.debug("remove cached query result from redis");
  return null;
 }
 /**
  * clears this cache instance
  */
 @override
 public void clear() {
  redistemplate redistemplate = getredistemplate();
  redistemplate.execute((rediscallback) connection -> {
   connection.flushdb();
   return null;
  });
  logger.debug("clear all the cached query result from redis");
 }
 @override
 public int getsize() {
  return 0;
 }
 @override
 public readwritelock getreadwritelock() {
  return readwritelock;
 }
 private redistemplate getredistemplate() {
  if (redistemplate == null) {
   redistemplate = applicationcontextholder.getbean("redistemplate");
  }
  return redistemplate;
 }
}

講解一下上述代碼中一些關(guān)鍵點:

自己實現(xiàn)的二級緩存,必須要有一個帶id的構(gòu)造函數(shù),否則會報錯。

我們使用spring封裝的redistemplate來操作redis。網(wǎng)上所有介紹redis做二級緩存的文章都是直接用jedis庫,但是筆者認(rèn)為這樣不夠spring style,而且,redistemplate封裝了底層的實現(xiàn),未來如果我們不用jedis了,我們可以直接更換底層的庫,而不用修改上層的代碼。更方便的是,使用redistemplate,我們不用關(guān)心redis連接的釋放問題,否則新手很容易忘記釋放連接而導(dǎo)致應(yīng)用卡死。

需要注意的是,這里不能通過autowire的方式引用redistemplate,因為rediscache并不是spring容器里的bean。所以我們需要手動地去調(diào)用容器的getbean方法來拿到這個bean,具體的實現(xiàn)方式請參考github中的代碼。

我們采用的redis序列化方式是默認(rèn)的jdk序列化。所以數(shù)據(jù)庫的查詢對象(比如product類)需要實現(xiàn)serializable接口。
這樣,我們就實現(xiàn)了一個優(yōu)雅的、科學(xué)的并且具有spring style的redis緩存類。

開啟二級緩存

接下來,我們需要在productmapper.xml中開啟二級緩存:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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.wooyoo.learning.dao.mapper.productmapper">
 <!-- 開啟基于redis的二級緩存 -->
 <cache type="com.wooyoo.learning.util.rediscache"/>
 <select id="select" resulttype="product">
  select * from products where id = #{id} limit 1
 </select>
 <update id="update" parametertype="product" flushcache="true">
  update products set name = #{name}, price = #{price} where id = #{id} limit 1
 </update>
</mapper>

<cache type="com.wooyoo.learning.util.rediscache"/>表示開啟基于redis的二級緩存,并且在update語句中,我們設(shè)置flushcache為true,這樣在更新product信息時,能夠自動失效緩存(本質(zhì)上調(diào)用的是clear方法)。

測試

配置h2內(nèi)存數(shù)據(jù)庫

至此我們已經(jīng)完成了所有代碼的開發(fā),接下來我們需要書寫單元測試代碼來測試我們代碼的質(zhì)量。我們剛才開發(fā)的過程中采用的是mysql數(shù)據(jù)庫,而一般我們在測試時經(jīng)常采用的是內(nèi)存數(shù)據(jù)庫。這里我們使用h2作為我們測試場景中使用的數(shù)據(jù)庫。

要使用h2也很簡單,只需要跟使用mysql時配置一下即可。在application.yml文件中:

?
1
2
3
4
5
6
7
8
9
10
11
---
spring:
 profiles: test
 # 數(shù)據(jù)庫配置
 datasource:
 url: jdbc:h2:mem:test
 username: root
 password: 123456
 driver-class-name: org.h2.driver
 schema: classpath:schema.sql
 data: classpath:data.sql

為了避免和默認(rèn)的配置沖突,我們用---另起一段,并且用profiles: test表明這是test環(huán)境下的配置。然后只要在我們的測試類中加上@activeprofiles(profiles = "test")注解來啟用test環(huán)境下的配置,這樣就能一鍵從mysql數(shù)據(jù)庫切換到h2數(shù)據(jù)庫。

在上述配置中,schema.sql用于存放我們的建表語句,data.sql用于存放insert的數(shù)據(jù)。這樣當(dāng)我們測試時,h2就會讀取這兩個文件,初始化我們所需要的表結(jié)構(gòu)以及數(shù)據(jù),然后在測試結(jié)束時銷毀,不會對我們的mysql數(shù)據(jù)庫產(chǎn)生任何影響。這就是內(nèi)存數(shù)據(jù)庫的好處。另外,別忘了在pom.xml中將h2的依賴的scope設(shè)置為test。

使用spring boot就是這么簡單,無需修改任何代碼,輕松完成數(shù)據(jù)庫在不同環(huán)境下的切換。

編寫測試代碼

因為我們是通過spring initializer初始化的項目,所以已經(jīng)有了一個測試類——springbootmybatiswithredisapplicationtests。

spring boot提供了一些方便我們進(jìn)行web接口測試的工具類,比如testresttemplate。然后在配置文件中我們將log等級調(diào)成debug,方便觀察調(diào)試日志。具體的測試代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@runwith(springrunner.class)
@springboottest(webenvironment = springboottest.webenvironment.random_port)
@activeprofiles(profiles = "test")
public class springbootmybatiswithredisapplicationtests {
 @localserverport
 private int port;
 @autowired
 private testresttemplate resttemplate;
 @test
 public void test() {
  long productid = 1;
  product product = resttemplate.getforobject("http://localhost:" + port + "/product/" + productid, product.class);
  assertthat(product.getprice()).isequalto(200);
  product newproduct = new product();
  long newprice = new random().nextlong();
  newproduct.setname("new name");
  newproduct.setprice(newprice);
  resttemplate.put("http://localhost:" + port + "/product/" + productid, newproduct);
  product testproduct = resttemplate.getforobject("http://localhost:" + port + "/product/" + productid, product.class);
  assertthat(testproduct.getprice()).isequalto(newprice);
 }
}

在上述測試代碼中:

我們首先調(diào)用get接口,通過assert語句判斷是否得到了預(yù)期的對象。此時該product對象會存入redis中。

然后我們調(diào)用put接口更新該product對象,此時redis緩存會失效。

最后我們再次調(diào)用get接口,判斷是否獲取到了新的product對象。如果獲取到老的對象,說明緩存失效的代碼執(zhí)行失敗,代碼存在錯誤,反之則說明我們代碼是ok的。

書寫單元測試是一個良好的編程習(xí)慣。雖然會占用你一定的時間,但是當(dāng)你日后需要做一些重構(gòu)工作時,你就會感激過去寫過單元測試的自己。

查看測試結(jié)果

我們在intellij中點擊執(zhí)行測試用例,測試結(jié)果如下:

通過Spring Boot + Mybatis + Redis快速搭建現(xiàn)代化Web項目

顯示的是綠色,說明測試用例執(zhí)行成功了。

總結(jié)

本篇文章介紹了如何通過spring boot、mybatis以及redis快速搭建一個現(xiàn)代化的web項目,并且同時介紹了如何在spring boot下優(yōu)雅地書寫單元測試來保證我們的代碼質(zhì)量。當(dāng)然這個項目還存在一個問題,那就是mybatis的二級緩存只能通過flush整個db來實現(xiàn)緩存失效,這個時候可能會把一些不需要失效的緩存也給失效了,所以具有一定的局限性。

原文鏈接:http://www.roncoo.com/article/detail/131302

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 俺去俺也在线www色官网 | 亚洲AV午夜福利精品香蕉麻豆 | 成人在线观看网站 | 免费国产成人α片 | 国产欧美精品一区二区三区四区 | 免费国产在线观看 | 精品国产无限资源免费观看 | 华人亚洲欧美精品国产 | 免费看h片的网站 | 骚虎最新网址 | 欧美添下面视频免费观看 | 1024人成网站色 | 久久99精品久久久久久园产越南 | 成人欧美视频在线看免费 | 陈峰姚瑶全集小说无删节 | 欧美日韩国产精品自在自线 | 欧美一级特黄刺激大片视频 | 国内精品视频一区二区三区八戒 | 精品久久香蕉国产线看观看麻豆 | 91在线精品老司机免费播放 | 草莓秋葵菠萝蜜绿巨人污 | 特级夫妻大片免费在线播放 | 故意短裙公车被强好爽在线播放 | 天天操免费视频 | 亚洲一区二区精品推荐 | 国产ab| 黑人巨摘花第一次出血 | 青苹果乐园影院免费观看完整版 | 亚洲国产精品线在线观看 | 欧美无专区 | 久久午夜夜伦痒痒想咳嗽P 久久无码AV亚洲精品色午夜麻豆 | 久青草国产在视频在线观看 | 高h全肉动漫在线观看免费 高h辣h双处全是肉军婚 | 日本一卡二卡3卡四卡网站精品 | 免费免费啪视频在线观播放 | 久久亚洲午夜牛牛影视 | 男男playh片在线观看 | 国产精品久久久久久爽爽爽 | 亚洲天堂2013 | 胸奶好大好紧好湿好爽 | 亚州第一页 |