Spring Boot 入門之持久層篇(三)


博客地址:http://www.moonxy.com

一、前言

當前數據庫的持久層框架主要分為兩種架構模式,即以 SQL 為中心和以對象為中心。

Spring JDBC Template 和 MyBatis 等數據庫持久層框架,都是以 SQL 為核心,而 Spring Data 和 Hibernate 等,則是以對象為核心的持久層框架。Spring Data JPA 是 Spring Data 的一個子項目,主要用於簡化數據訪問層的實現,使用 Spring Data JPA 可以輕松實現增刪改查、分頁和排序等。

二、整合 Spring JDBC Template

2.1 配置數據源

這里使用坊間流傳的最快的數據庫連接池 HikariCP 來整合 Spring JDBC Template,集成到 Spring Boot,添加 pom 依賴:

<!-- HikariCP -->
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>2.7.2</version>
</dependency>

然后在配置文件中配置連接數據庫的基本信息以供 HikariCP 使用:

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=123456

配置 MySQL 的驅動依賴:

<!-- MySQL -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.5</version>
</dependency>

配置好上述依賴包和配置文件后,還需要編寫一個 Java Config 來創建一個數據源,如下:

package com.light.springboot.conf;


import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import com.zaxxer.hikari.HikariDataSource;

@Configuration
public class DataSourceConfig {
    
    @Bean(name="dataSource")
    public DataSource datasource(Environment env) {
        HikariDataSource ds = new HikariDataSource();
        ds.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
        ds.setJdbcUrl(env.getProperty("spring.datasource.url"));
        ds.setUsername(env.getProperty("spring.datasource.username"));
        ds.setPassword(env.getProperty("spring.datasource.password"));
        return ds;
    }
}

2.2 建表

此處使用 MySQL 數據庫,創建兩張表結構:

--用戶表
create table user(
id int(11) not null auto_increment,
name varchar(45) collate utf8_bin default null comment '名稱',
department_id int(11) default null,
create_time date default null comment '創建時間',
primary key (id)
)ENGINE=innoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

--部門表
create table department(
id int(11) not null auto_increment,
name varchar(45) collate utf8_bin default null,
primary key (id)
)ENGINE=innoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

--測試數據
insert into department(name) values("Development");
insert into department(name) values("Test");
insert into department(name) values("Production");

2.3 建立實體類

建議實體類實現序列化接口,如下:

package com.light.springboot.entity;

import java.io.Serializable;
import java.util.Date;

import com.fasterxml.jackson.annotation.JsonFormat;

public class User implements Serializable {
    
    private static final long serialVersionUID = 8886402739972726962L;
    
    private int id;
    private String name;
    private int departmentId;
    
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", locale = "zh" , timezone="GMT+8")
    private Date createDate;

    @Override
    public String toString() {
        return "UserEntity [id=" + id + ", name=" + name + ", departmentId=" + departmentId + ", createDate=" + createDate + "]";
    }

    // 省略 Setter 和 Getter 方法
}

2.4 建立接口和實現類

接口提供常用的 CRUD 方法定義,如下:

package com.light.springboot.dao;

import java.util.List;

import com.light.springboot.entity.User;

public interface UserDao {
    
    public Integer insertUser(User user);
    
    public Integer deleteUserById(Integer id);
    
    public Integer updateUserById(User user);
    
    public User getUserById(Integer id);
    
    public List<User> getUserByDepartmentId(Integer id);
    
    public Integer getUserCountByDepartmentId(Integer departmentId);
    
    public Integer newUpdateUserById(User user);
}

Spring 提供了 JdbcTemplate 對數據庫訪問技術 JDBC 做了一定封裝,包括管理數據庫連接,簡單查詢結果映射成 Java 對象,復雜的結果集通過實現 RowMapper 接口來映射到 Java 對象。在 Spring Boot 中,只要配置好數據源 DataSource,就能自動使用 JdbcTemplate(org.springframework.jdbc.core.JdbcTemplate),查看 JdbcTemplate 源碼后,發現其提供了 dataSource 的 Set 注入方法: setDataSource(dataSource);所以 Spring 容器中只要存在 dataSource 的 bean,便會自動裝配 JdbcTemplate 類。

實現類提供了上面接口的 CRUD 方法的實現過程,@Repository 注解通過作用在和存儲相關的實現類上,如下:

package com.light.springboot.dao.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;

import com.light.springboot.dao.UserDao;
import com.light.springboot.entity.User;

@Repository
public class UserDaoImpl implements UserDao {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    
    /**
     * JdbcTemplate提供的update方法,可以用於新增、修改、刪除、執行存儲過程等
     * 如果是數據庫插入,對於MySQL、SQL Server等數據庫,含有自增序列時,則需要提供一個KeyHolder來存放返回的序列
     */
    @Override
    public Integer insertUser(User user) {
        String sql = "insert into user(name, department_id, create_time) values(?,?,?)";
        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbcTemplate.update(new PreparedStatementCreator() {
            public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
                // 指出自增主鍵的列名
                PreparedStatement ps = connection.prepareStatement(sql, new String[] {"id"});
                ps.setString(1, user.getName());
                ps.setInt(2, user.getDepartmentId());
                ps.setDate(3, new java.sql.Date(new java.util.Date().getTime()));
                return ps;
            }
        }, keyHolder);
        return keyHolder.getKey().intValue();
    }

    @Override
    public Integer deleteUserById(Integer id) {
        String sql = "delete from user where id = ?";
        return this.jdbcTemplate.update(sql,id);
    }

    @Override
    public Integer updateUserById(User user) {
        String sql = "update user set name = ?, department_id=? where id = ?";
        return jdbcTemplate.update(
                sql, 
                user.getName(),
                user.getDepartmentId(),
                user.getId()
                );
    }
    
    /**
     * JdbcTemplate需要一個RowMapper,將查詢結果集ResultSet映射成一個對象
     */
    @Override
    public User getUserById(Integer id) {
        String sql = "select * from user where id = ?";
        return jdbcTemplate.queryForObject(sql, new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet rs, int rowNum) throws SQLException {
                User user = new User();
                user.setId(rs.getInt("id"));
                user.setName(rs.getString("name"));
                user.setDepartmentId(rs.getInt("department_id"));
                user.setCreateDate(rs.getDate("create_time"));
                return user;
            }
            
        },id);
    }
    
    /**
     * 也可以創建一個內部類UserRowMapper作為查詢復用
     * @author Administrator
     *
     */
    static class UserRowMapper implements RowMapper<User>{
        public User mapRow(ResultSet rs, int rowNum) throws SQLException {
            User user = new User();
            user.setId(rs.getInt("id"));
            user.setName(rs.getString("name"));
            user.setDepartmentId(rs.getInt("department_id"));
            user.setCreateDate(rs.getDate("create_time"));
            return user;
        }
    }
    
    /**
     * 如果返回的結果為列表,則需要使用query方法
     */
    @Override
    public List<User> getUserByDepartmentId(Integer departmentId) {
        String sql = "select * from user where department_id = ?";
        List<User> userList = jdbcTemplate.query(sql, new UserRowMapper(), departmentId);
        return userList;
    }

    /**
     * MapSqlParameterSource是一個類似Map風格的類,包括 Key-Value,Key就是SQL中的參數
     */
    @Override
    public Integer getUserCountByDepartmentId(Integer departmentId) {
        String sql = "select count(1) from user where department_id = :deptId";
        MapSqlParameterSource namedParameters = new MapSqlParameterSource();
        namedParameters.addValue("deptId", departmentId);
        Integer count = namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
        return count;
    }
    
    /**
     * 使用SqlParameterSource來封裝任意的JavaBean
     */
    @Override
    public Integer newUpdateUserById(User user) {
        String sql = "update user set name = :name, department_id = :departmentId where id = :id";
        SqlParameterSource source = new BeanPropertySqlParameterSource(user);
        return namedParameterJdbcTemplate.update(sql, source);
    }
    
}

2.5 Spring Boot 單元測試

Spring Boot 單元測試需要添加如下依賴:

<!-- Spring Boot 單元測試 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>        

在 test/java 目錄添加單元測試代碼,如下:

import java.util.Date;
import java.util.List;

import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.light.springboot.SpringbootApplication;
import com.light.springboot.dao.UserDao;
import com.light.springboot.entity.User;

@RunWith(SpringRunner.class)
@SpringBootTest(classes=SpringbootApplication.class)
public class UserDaoTest {

    @Autowired
    private UserDao userDao;

    /**
     * 新增用戶
     */
    @Test
    @Ignore
    public void testInsertUser() {
        User user = new User();
        user.setName("Bob");
        user.setDepartmentId(2);
        user.setCreateDate(new Date());
        
        int result = this.userDao.insertUser(user);
        System.out.println(result);
    }
    
    /**
     * 通過id查找單個用戶
     */
    @Test
    @Ignore
    public void testGetUserById() {
        User user = this.userDao.getUserById(1);
        System.out.println(user.getName());
    }
    
    /**
     * 通過id修改單個用戶
     */
    @Test
    @Ignore
    public void testUpdateUserById() {
        User user = new User();
        user.setId(1);
        user.setName("Deft");
        user.setDepartmentId(2);
        this.userDao.updateUserById(user);
    }
    
    /**
     * 通過id刪除單個用戶
     */
    @Test
    @Ignore
    public void testDeleteUserById() {
        int result = this.userDao.deleteUserById(1);
        System.out.println(result);
    }
    
    /**
     * 通過部門id查找多個用戶
     */
    @Test
    @Ignore
    public void testGetUserByDepartmentId() {
        List<User> userList = this.userDao.getUserByDepartmentId(1);
        System.out.println(userList.size());
    }
    
    /**
     * 使用MapSqlParameterSource
     */
    @Test
    @Ignore
    public void testGetUserCountByDepartmentId() {
        Integer userCount = this.userDao.getUserCountByDepartmentId(1);
        System.out.println(userCount);
    }
    
    /**
     * 使用MapSqlParameterSource
     */
    @Test
    @Ignore
    public void testNewUpdateUserById() {
        User user = new User();
        user.setId(1);
        user.setName("Rain");
        user.setDepartmentId(2);
        Integer count = this.userDao.newUpdateUserById(user);
        System.out.println(count);
    }
}

其中,@RunWith 是 JUnit 標准的一個注解,用來告訴 JUnit 單元測試框架不要使用內置的方式進行單元測試,而應使用 RunWith 指明的類來提供單元測試,所有的 Spring 單元測試總是使用 SpringRunner.class。

@SpringBootTest 用於 Spring Boot 應用測試,它默認會根據包名逐級網上找,當測試類和主程序在同一個包下時,可以不用配置 classes,Spring 會一直找到 Spring Boot 主程序,也就是通過類注解是否包含@SpringBootApplication 來判斷是否是主程序,並在單元測試的時候啟動該類來創建 Spring 上下文環境。但當測試類和主程序不在同一個包下時,需要添加 classes,指定主程序啟動類,如 classes=SpringbootApplication.class。

@Ignore 表示在測試時要跳過的方法,測試時可以單個測試。

2.6 JdbcTemplate 增強

NamedParameterJdbcTemplate 繼承了 JdbcTemplate,JdbcTemplate 對 SQL 中的參數只支持傳統的 "?" 占位符。NamedParameterJdbcTemplate 允許 SQL 中使用參數的名字作為占位符。

MapSqlParameterSource是一個類似Map風格的類,包括 Key-Value,Key就是SQL中的參數,如上面的:getUserCountByDepartmentId 方法;

Spring 還提供了 SqlParameterSource 類來封裝任意的 JavaBean,為 NamedParameterJdbcTemplate 提供參數,JavaBean 的屬性名稱作為 SQL 的參數名字,如上面的:newUpdateUserById 方法。

三、整合 Spring Data JPA

Spring Data JPA 采用了 Hibernate 實現,以對象為中心,屬於 ORM(Object Relational Mapping)工具,對實體和實體關系的操作會映射到數據庫操作。

3.1 添加依賴

添加 Spring Data JPA、HikariCP 數據源連接池和 MySQL 數據庫驅動依賴,如下:

<!-- Spring Data JPA -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- HikariCP -->
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>2.7.2</version>
</dependency>

<!-- MySQL -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.5</version>
</dependency>

3.2 數據庫連接配置和 JPA 配置

# 數據庫連接配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=123456

# JPA 配置
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

spring.jpa.hibernate.ddl-auto,是否自動建庫,默認為 none。有如下取值可以選擇:

create:每次加載hibernate時都會刪除上一次的生成的表,然后根據你的model類再重新來生成新表,哪怕兩次沒有任何改變也要這樣執行,這就是導致數據庫表數據丟失的一個重要原因;

create-drop:每次加載hibernate時根據model類生成表,但是sessionFactory一關閉,表就自動刪除;

update:最常用的屬性,第一次加載hibernate時根據model類會自動建立起表的結構(前提是先建立好數據庫),以后加載hibernate時根據model類自動更新表結構,即使表結構改變了但表中的行仍然存在不會刪除以前的行。要注意的是當部署到服務器后,表結構是不會被馬上建立起來的,是要等應用第一次運行起來后才會;

validate:每次加載hibernate時,驗證創建數據庫表結構,只會和數據庫中的表進行比較,不會創建新表,但是會插入新值;

也可將其設置為 none,表示框架不執行任何操作。

spring.jpa.show-sql,是否自動打印 SQL,默認為 false。

配置好上述依賴包和配置文件后,還需要編寫一個 Java Config 來創建一個數據源,如下:

package com.light.springboot.conf;


import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import com.zaxxer.hikari.HikariDataSource;

@Configuration
public class DataSourceConfig {
    
    @Bean(name="dataSource")
    public DataSource datasource(Environment env) {
        HikariDataSource ds = new HikariDataSource();
        ds.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
        ds.setJdbcUrl(env.getProperty("spring.datasource.url"));
        ds.setUsername(env.getProperty("spring.datasource.username"));
        ds.setPassword(env.getProperty("spring.datasource.password"));
        return ds;
    }
}

3.3 建立實體類

現在越來越多的 JPA 應用采用簡化的 Entity 定義,去掉了關系映射的相關配置,去掉了數據庫外鍵設置。一個表對應一個簡單的實體,這樣弱關聯使用 JPA 更加容易。

package com.light.springboot.entity;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import com.fasterxml.jackson.annotation.JsonFormat;

@Entity
public class User implements Serializable {
    
    private static final long serialVersionUID = 8886402739972726962L;
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;
    
    @Column
    private String name;
    
    @Column(name="department_id")
    private int departmentId;
    
    @Column(name="create_time")
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", locale = "zh" , timezone="GMT+8")
    private Date createDate;
    
    @Override
    public String toString() {
        return "UserEntity [id=" + id + ", name=" + name + ", departmentId=" + departmentId + ", createDate=" + createDate + "]";
    }

    // 省略 Setter 和 Getter 方法
}

其中,@Entity 注解指名這是一個實體 Bean,@Table 注解指定了 Entity 所要映射的數據庫表,其中@Table.name()用來指定映射表的表名。如果缺省 @Table 注解,系統默認采用類名作為映射表的表名。實體 Bean 的每個實例代表數據表中的一行數據,行中的一列對應實例中的一個屬性。

3.4 建立接口

Spring Data 提供如下接口供開發者使用:

Repository:僅僅是一個標識,表明任何繼承它的均為倉庫接口類,方便 Spring 自動掃描識別;

CrudRepository:繼承Repository,實現了一組 CRUD 相關的方法;

PagingAndSortingRepository:繼承 CrudRepository,實現了一組分頁排序相關的方法;

JpaRepository:繼承 PagingAndSortingRepository,實現一組 JPA 規范相關的方法;

JpaSpecificationExecutor:比較特殊,不屬於Repository體系,實現一組 JPA Criteria 查詢相關的方法;

即 JpaRepository 繼承 PagingAndSortingRepository 繼承 CrudRepository 繼承 Repository。

我們自己定義的 XxxxRepository 需要繼承 JpaRepository,這樣我們的 XxxxRepository 接口就具備了通用的數據訪問控制層的能力。

package com.light.springboot.dao;

import org.springframework.data.jpa.repository.JpaRepository;

import com.light.springboot.entity.User;

public interface UserRepository extends JpaRepository<User, Integer> {

}

接口中不需要任何方法的聲明,查看 JpaRepository 源碼,發現已經提供好了常用的基本方法,已經具備了實現存取數據庫的功能了,可以對數據庫進行增刪改查、進行分頁查詢和指定排序的字段等操作了。

3.5 基於方法名字查詢

Spring 還提供了通過查詢的方法名和參數名來自動構造一個 JPA OQL(JPA 對象查詢語言)查詢,可以在如上的 UserRepository 中添加幾個查詢方法,如:

package com.light.springboot.dao;

import java.sql.Date;
import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import com.light.springboot.entity.User;

public interface UserRepository extends JpaRepository<User, Integer> {
    
    public List<User> findByName(String name);
    
    public List<User> queryByIdOrName(String id, String name);
    
    public List<User> queryByCreateDateGreaterThanEqual(Date start);
}

方法名和參數名需要遵守一定的規則,Spring Data JPA 才能自動轉化為 JPQL。方法名可以使用 findBy、getBy、queryBy 和 readBy 開頭,作為方法名的前綴,拼接實體類中的屬性字段(首個字母大寫),並可選拼接一些 SQL 查詢關鍵字來組合成一個查詢方法。

3.6 使用 @Query 注解

也可以在上面的的 UserRepository 接口方法中添加 @Query 注解來使用 JPQL,如下:

@Query("select u from User u where u.name=?1 and u.departmentId=?2")
public User findUser(String name, Integer departmentId);

@Query(value="select * from user where name=?1 and department_id=?2", nativeQuery=true)
public User findUserByNativeQuery(String name, Integer departmentId);

3.7 創建單元測試類

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit4.SpringRunner;

import com.light.springboot.SpringbootApplication;
import com.light.springboot.dao.UserRepository;
import com.light.springboot.entity.User;

@RunWith(SpringRunner.class)
@SpringBootTest(classes=SpringbootApplication.class)
public class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    /**
     * 新增用戶
     */
    @Test
    @Ignore
    public void testInsertUser() {
        User user = new User();
        user.setName("Jim");
        user.setDepartmentId(2);
        user.setCreateDate(new Date());
        
        User resultUser = this.userRepository.save(user);
        System.out.println(resultUser.toString());
    }
    
    /**
     * 通過id查找單個用戶
     */
    @Test
    @Ignore
    public void testGetUserById() {
        User user = this.userRepository.findOne(2);
        System.out.println(user.toString());
    }
    
    /**
     * 通過id修改單個用戶
     */
    @Test
    @Ignore
    public void testUpdateUserById() {
        User user = this.userRepository.findOne(2);
        user.setId(2);
        user.setName("Deft");
        user.setDepartmentId(2);
        this.userRepository.save(user);
    }
    
    /**
     * 通過id刪除單個用戶
     */
    @Test
    @Ignore
    public void testDeleteUserById() {
        this.userRepository.delete(2);
    }
    
    /**
     * 查找所有的用戶
     */
    @Test
    @Ignore
    public void testGetAllUser() {
        Sort sort = new Sort(Sort.Direction.DESC, "id");//默認升序,可以指定為按照id降序
        List<User> userList = this.userRepository.findAll(sort);
        System.out.println(userList.size());
    }
    
    /**
     * 自定義方法:通過name查詢
     */
    @Test
    @Ignore
    public void testFindByName() {
        List<User> userList = this.userRepository.findByName("Adam");
        System.out.println(userList.size());
    }
    
    /**
     * 自定義方法:通過id或name查詢
     */
    @Test
    @Ignore
    public void testQueryByIdOrName() {
        List<User> userList = this.userRepository.queryByIdOrName(3, "Adam");
        System.out.println(userList.size());
    }
    
    /**
     * 自定義方法:大於等於某個創建日期的用戶
     * @throws ParseException 
     */
    @Test
    @Ignore
    public void testQueryByCreateDateGreaterThanEqual() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Date date = sdf.parse("2018-02-03");
        List<User> userList = this.userRepository.queryByCreateDateGreaterThanEqual(date);
        System.out.println(userList.size());
    }
    
    /**
     * 使用@Query注解實現自定義JPA SQL
     */
    @Test
    @Ignore
    public void testFindUser() {
        User user = this.userRepository.findUser("Adam", 1);
        System.out.println(user.toString());
    }
    
    /**
     * 使用@Query注解實現自定義原生SQL
     */
    @Test
@Ignore
public void testFindUserByNativeQuery() { User user = this.userRepository.findUser("Bob", 2); System.out.println(user.toString()); } }

四、常見數據庫連接池對比

在日常開發中,常見的開源數據庫連接池有:Hikari、Druid、C3p0、Dbcp 和 Tomcat-jdbc,由於 BoneCP 被 HikariCP 替代,並且已經不再更新,所以對 BoneCP 沒有進行過多研究。Proxool 網上有評測說在並發較高的情況下會出錯,且使用的人較少,所以對 Proxool 也沒有進行過多研究。Druid的功能比較全面,且擴展性較好,比較方便對 Jdbc 接口進行監控跟蹤等。C3p0 歷史悠久,代碼及其復雜,不利於維護。並且存在 Deadlock 的潛在風險。

綜合結論:

1:性能方面 HikariCP > Druid > Tomcat-jdbc > Dbcp > C3p0 。hikariCP的高性能得益於最大限度的避免鎖競爭。

2:Druid 功能最為全面,sql攔截等功能,統計數據較為全面,具有良好的擴展性。

3:綜合性能,擴展性等方面,可考慮使用 Druid 或者 HikariCP連接池。

4:可開啟 prepareStatement 緩存,對性能會有大概 20% 的提升。

上面列舉了幾種開源的連接池,其實還有商業中間件連接池,比如 weblogic 和 websphere 等中間件連接池,這些連接池功能強大,穩定性好,性能優秀,而且監控到位。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM