Spring Boot 集成 Druid 數(shù)據(jù)源
1. 前言
首先要理解數(shù)據(jù)源的作用,數(shù)據(jù)源實(shí)際上是一個(gè)接口 javax.sql.DataSource
。 Spring Boot 在操作數(shù)據(jù)庫(kù)時(shí),是通過(guò)數(shù)據(jù)源類型的組件實(shí)現(xiàn)的。
數(shù)據(jù)源的類型有很多,但是都實(shí)現(xiàn)了 javax.sql.DataSource
接口,所以 Spring Boot 可以盡情地更換各類數(shù)據(jù)源以實(shí)現(xiàn)不同的需求。
其中 Druid 數(shù)據(jù)源就是數(shù)據(jù)源中比較出類拔萃的存在,而且是阿里開發(fā)的。國(guó)人出品的東西,咱們能支持的必須得支持啊,當(dāng)然這是建立在人家做的確實(shí)好的基礎(chǔ)上。
本篇我們使用 Druid 替換默認(rèn)的數(shù)據(jù)源,然后做一下性能對(duì)比測(cè)試。網(wǎng)上有很多文章寫 Druid 性能如何如何強(qiáng)悍,但是很多并沒(méi)有事實(shí)依據(jù)。我們做程序員還是要嚴(yán)謹(jǐn),相信實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)。所以本篇的內(nèi)容就是研究下 Druid 如何使用,及其性能到底是否足夠優(yōu)異。
2. 使用默認(rèn)數(shù)據(jù)源(HikariDataSource)
Spring Boot 2.2.5 版本使用的默認(rèn)數(shù)據(jù)源是 HikariDataSource ,該數(shù)據(jù)源號(hào)稱擁有全世界最快的數(shù)據(jù)庫(kù)連接池,嗯,我們來(lái)試試它的深淺。
2.1 準(zhǔn)備數(shù)據(jù)庫(kù)
還是使用之前的商城數(shù)據(jù)庫(kù)(shop)及商品信息數(shù)據(jù)表(goods),表結(jié)構(gòu)如下:
實(shí)例:
CREATE TABLE `goods` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '唯一編號(hào)',
`name` varchar(255) DEFAULT '' COMMENT '商品名稱',
`price` decimal(10,2) DEFAULT '0.00' COMMENT '商品價(jià)格',
`pic` varchar(255) DEFAULT '' COMMENT '圖片文件名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.2 使用 Spring Initializr 創(chuàng)建項(xiàng)目
Spring Boot 版本選擇 2.2.5 , Group 為 com.imooc
, Artifact 為 spring-boot-hikari
,生成項(xiàng)目后導(dǎo)入 Eclipse 開發(fā)環(huán)境。
2.3 引入項(xiàng)目依賴
我們引入 Web 項(xiàng)目依賴、熱部署依賴。由于本項(xiàng)目需要訪問(wèn)數(shù)據(jù)庫(kù),所以引入 spring-boot-starter-jdbc
依賴和 mysql-connector-java
依賴。
pom.xml 文件中依賴項(xiàng)如下:
實(shí)例:
<!-- 熱部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- jdbc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- myql驅(qū)動(dòng) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2.4 構(gòu)建商品類和商品數(shù)據(jù)訪問(wèn)類
定義商品類,對(duì)應(yīng)商品表:
實(shí)例:
/**
* 商品類
*/
public class GoodsDo {
/**
* 商品id
*/
private Long id;
/**
* 商品名稱
*/
private String name;
/**
* 商品價(jià)格
*/
private String price;
/**
* 商品圖片
*/
private String pic;
// 省略get set方法
}
定義商品數(shù)據(jù)庫(kù)訪問(wèn)類:
實(shí)例:
/**
* 商品數(shù)據(jù)庫(kù)訪問(wèn)類
*/
@Repository // 標(biāo)注數(shù)據(jù)訪問(wèn)類
public class GoodsDao {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 新增
*/
public void insert(GoodsDo goods) {
jdbcTemplate.update("insert into goods(name,price,pic)values(?,?,?)", goods.getName(), goods.getPrice(),
goods.getPic());
}
/**
* 刪除
*/
public void delete(Long id) {
jdbcTemplate.update("delete from goods where id =?", id);
}
/**
* 更新
*/
public void update(GoodsDo goods) {
jdbcTemplate.update("update goods set name=?,price=?,pic=? where id=?", goods.getName(), goods.getPrice(),
goods.getPic(), goods.getId());
}
/**
* 按id查詢
*/
public GoodsDo getById(Long id) {
return jdbcTemplate.queryForObject("select * from goods where id=?", new RowMapper<GoodsDo>() {
@Override
public GoodsDo mapRow(ResultSet rs, int rowNum) throws SQLException {
GoodsDo goods = new GoodsDo();
goods.setId(rs.getLong("id"));
goods.setName(rs.getString("name"));
goods.setPrice(rs.getString("price"));
goods.setPic(rs.getString("pic"));
return goods;
}
}, id);
}
/**
* 查詢商品列表
*/
public List<GoodsDo> getList() {
return jdbcTemplate.query("select * from goods", new RowMapper<GoodsDo>() {
@Override
public GoodsDo mapRow(ResultSet rs, int rowNum) throws SQLException {
GoodsDo goods = new GoodsDo();
goods.setId(rs.getLong("id"));
goods.setName(rs.getString("name"));
goods.setPrice(rs.getString("price"));
goods.setPic(rs.getString("pic"));
return goods;
}
});
}
}
2.5 配置數(shù)據(jù)源信息
通過(guò)配置文件,設(shè)置數(shù)據(jù)源信息。
實(shí)例:
# 配置數(shù)據(jù)庫(kù)驅(qū)動(dòng)
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 配置數(shù)據(jù)庫(kù)url
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/shop?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
# 配置數(shù)據(jù)庫(kù)用戶名
spring.datasource.username=root
# 配置數(shù)據(jù)庫(kù)密碼
spring.datasource.password=Easy@0122
2.6 測(cè)試
通過(guò)測(cè)試類發(fā)起測(cè)試,此處我們簡(jiǎn)單執(zhí)行 1000 次插入,看看執(zhí)行時(shí)間。
需要注意的是,Spring Boot 進(jìn)行測(cè)試時(shí),需要添加注解 @SpringBootTest
。添加注解后該類可以直接通過(guò) @Test
標(biāo)注的方法發(fā)起單元測(cè)試,容器環(huán)境都已準(zhǔn)備好,非常方便。
實(shí)例:
@SpringBootTest // 通過(guò)該注解,開啟測(cè)試類功能,當(dāng)測(cè)試方法啟動(dòng)時(shí),啟動(dòng)了Spring容器
class SpringBootHikariApplicationTests {
@Autowired
private DataSource dataSource;// 自動(dòng)注入數(shù)據(jù)源
@Autowired
private GoodsDao goodsDao;
/**
* 打印數(shù)據(jù)源信息
*/
@Test // 測(cè)試方法
void printDataSource() {
System.out.println(dataSource);
}
/**
* 批量插入測(cè)試
*/
@Test
void insertBatch() {
// 開始時(shí)間
long startTime = System.currentTimeMillis();
// 執(zhí)行1000次插入
GoodsDo goods = new GoodsDo();
goods.setName("測(cè)試");
goods.setPic("測(cè)試圖片");
goods.setPrice("1.0");
for (int i = 0; i < 1000; i++) {
goodsDao.insert(goods);
}
// 輸出操作時(shí)間
System.out.println("use time:" + (System.currentTimeMillis() - startTime)+"ms");
}
}
輸出結(jié)果如下,可見(jiàn)默認(rèn)數(shù)據(jù)源類型為 HikariDataSource
,插入 1000 條數(shù)據(jù)的時(shí)間大概為 1500ms (注意時(shí)間可能跟電腦性能等很多因素相關(guān),此處只是進(jìn)行簡(jiǎn)單的對(duì)比測(cè)試)。
use time:1518ms
com.zaxxer.hikari.HikariDataSource
3. 使用 Druid 數(shù)據(jù)源
接下來(lái)我們使用 Druid 數(shù)據(jù)源進(jìn)行對(duì)比測(cè)試。
3.1 準(zhǔn)備數(shù)據(jù)庫(kù)
與上面的商城數(shù)據(jù)庫(kù)(shop)及商品信息數(shù)據(jù)表(goods)一致。
3.2 使用 Spring Initializr 創(chuàng)建項(xiàng)目
Spring Boot 版本選擇 2.2.5 , Group 為 com.imooc
, Artifact 為 spring-boot-druid
,生成項(xiàng)目后導(dǎo)入 Eclipse 開發(fā)環(huán)境。
3.3 引入項(xiàng)目依賴
我們引入 Web 項(xiàng)目依賴、熱部署依賴。由于本項(xiàng)目需要訪問(wèn)數(shù)據(jù)庫(kù),所以引入 spring-boot-starter-jdbc
依賴和 mysql-connector-java
依賴,由于使用 Druid ,所以還需要添加 Druid 相關(guān)依賴。
pom.xml 文件中依賴項(xiàng)如下:
實(shí)例:
<!-- 熱部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- jdbc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- myql驅(qū)動(dòng) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- springboot druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.22</version>
</dependency>
3.4 構(gòu)建商品類和商品數(shù)據(jù)訪問(wèn)類
與 spring-boot-hikari
項(xiàng)目一致。
3.5 配置數(shù)據(jù)源信息
通過(guò)配置文件,設(shè)置數(shù)據(jù)源信息。由于我們不再使用默認(rèn)數(shù)據(jù)源,所以此處需要指定數(shù)據(jù)源類型為 DruidDataSource 。
實(shí)例:
# 指定數(shù)據(jù)源類型
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# 配置數(shù)據(jù)庫(kù)驅(qū)動(dòng)
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 配置數(shù)據(jù)庫(kù)url
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/shop?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
# 配置數(shù)據(jù)庫(kù)用戶名
spring.datasource.username=root
# 配置數(shù)據(jù)庫(kù)密碼
spring.datasource.password=Easy@0122
3.6 測(cè)試
測(cè)試類代碼同 spring-boot-hikari
一致,運(yùn)行測(cè)試類后,結(jié)果如下:
use time:1428ms
com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceWrapper
4. 對(duì)比結(jié)果分析
其實(shí)只能得出一個(gè)結(jié)論,在某些場(chǎng)景下 Druid 的速度不比 Hikari 慢,甚至還略勝一籌。
當(dāng)然我們只是對(duì)兩種數(shù)據(jù)源的默認(rèn)配置、單一線程情況進(jìn)行了簡(jiǎn)單測(cè)試,大家感興趣的話可以研究?jī)煞N數(shù)據(jù)源的配置方式,然后通過(guò)多線程進(jìn)行全面測(cè)試。
5. Druid 監(jiān)控
看到這個(gè)結(jié)果,大家可能對(duì)本篇文章不滿了,說(shuō)了半天,也沒(méi)看出 Druid 好在哪兒啊,為啥還費(fèi)勁將默認(rèn)的 Hikari 更換掉呢。
不要著急,我們仔細(xì)看下官方介紹:
可以看到, Druid 是為監(jiān)控而生,說(shuō)明 Druid 最強(qiáng)大的功能實(shí)際上是監(jiān)控,接下來(lái)我們就演示下如何實(shí)現(xiàn) Druid 監(jiān)控。
添加監(jiān)控相關(guān)的配置類,需要注意的是我們?cè)O(shè)定了監(jiān)控功能的賬號(hào)和密碼。
實(shí)例:
/**
* Druid配置
*/
@Configuration
public class DruidConfig {
/**
* 注冊(cè)servletRegistrationBean
*/
@Bean
public ServletRegistrationBean servletRegistrationBean() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),
"/druid/*");
servletRegistrationBean.addInitParameter("allow", "");
// 賬號(hào)密碼
servletRegistrationBean.addInitParameter("loginUsername", "imooc");
servletRegistrationBean.addInitParameter("loginPassword", "123456");
servletRegistrationBean.addInitParameter("resetEnable", "true");
return servletRegistrationBean;
}
/**
* 注冊(cè)filterRegistrationBean
*/
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
// 添加過(guò)濾規(guī)則.
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
此時(shí)打開網(wǎng)址 http://127.0.0.1:8080/druid
即可顯示 Druid 登錄頁(yè)面:
我們使用指定的用戶名 imooc 密碼 123456 登錄后,即可查看各類監(jiān)控信息,內(nèi)容還是非常全面的,此處就不再展開介紹了。
6. 小結(jié)
在實(shí)際研發(fā)與生產(chǎn)測(cè)試過(guò)程中,使用 Druid 的情況還是非常多的, Druid 非常穩(wěn)定、性能也表現(xiàn)相當(dāng)優(yōu)異,更重要的是它提供了全面直觀的監(jiān)控手段,所以現(xiàn)階段還是推薦大家使用 Druid 。