Spring 采用純注解整合 MyBatis 和 Junit


Spring 整合 MyBatis 和 Junit 等第三方組件,可以通過 XML 配置方式,也可以通過純注解的配置方式。這里僅僅提供純注解的配置方式,因為絕大多數情況下,企業開發都采用注解配置方式,因為注解配置比較簡單方便,我個人也比較喜歡注解配置方式。

本篇博客不會詳細介紹所用到的 Spring 注解,網上資料一大堆,初級開發人員可以自行查找相關資料學習。這里主要是介紹 Spring 如何通過純注解的方式整合 MyBatis 和 Junit,在本篇博客的最后會提供 Demo 的源代碼。


一、搭建工程

新建一個 maven 項目,導入相關 jar 包,我導入的都是最新版本的 jar 包,內容如下:

有關具體的 jar 包地址,可以在 https://mvnrepository.com 上進行查詢。

<dependencies>
    <!--
    Spring 相關的 jar 包:
    spring-context 基礎核心 jar 包
    spring-jdbc 提供數據庫訪問的基礎核心 jar 包
    spring-test 提供測試的基本核心 jar 包,用於整合 junit
    -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.17</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.17</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.17</version>
        <scope>test</scope>
    </dependency>

    <!--
    連接操作數據庫相關的 jar 包:
    mysql-connector-java 連接 mysql 的 jar 包
    druid 阿里巴巴的數據庫連接池的 jar 包
    -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.8</version>
    </dependency>

    <!--
    MyBatis 的相關 jar 包:
    mybatis-spring 這個由 Mybatis 提供,讓 Spring 整合 Mybatis 的 jar 包
    -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.9</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.7</version>
    </dependency>

    <!--
    其它相關 jar 包:
    junit 單元測試方法編寫所需要的 jar 包
    commons-lang3 這個是 apache 提供的實用的公共類工具 jar 包
    -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.9</version>
    </dependency>
</dependencies>

打開右側的 Maven 窗口,刷新一下,這樣 Maven 會自動下載所需的 jar 包文件。

搭建好的項目工程整體目錄比較簡單,具體如下圖所示:

image

項目工程結構簡單介紹:

config 包下存放的是 Spring 的配置類

dao 包下存放的是 Mybatis 操作數據庫的接口類,以及復雜 SQL 語句的方法實現類

domain 包下存放是具體的 Java Bean 實體對象類

service 包下存放的是業務處理實現類

EmployeeApp 這個是該 demo 的 main 入口所在類,使用 Spring 訪問數據庫,驗證搭建成果

resources 目錄下存放的是連接數據庫的相關參數的配置文件

test 目錄下 EmployeeTest 這個是測試方法類,里面編寫了測試 EmployeeService 使用 MyBatis 訪問數據庫的所有方法


二、Spring 整合 MyBatis 的相關配置細節

本 demo 采用只采用一張表 employee 員工表進行演示,數據庫是 testdb ,具體創建的 sql 語句內容如下:

# 創建數據庫 testdb
CREATE DATABASE IF NOT EXISTS `testdb`;
USE `testdb`;

# 創建數據表 employee
# 字段為:主鍵id,員工姓名,薪水
CREATE TABLE IF NOT EXISTS `employee` (
  `e_id` int(11) NOT NULL AUTO_INCREMENT,
  `e_name` varchar(50) DEFAULT NULL,
  `e_salary` int(11) DEFAULT NULL,
  PRIMARY KEY (`e_id`)
) ENGINE=InnoDB AUTO_INCREMENT=103 DEFAULT CHARSET=utf8;

# 添加相關的測試數據
INSERT INTO `employee` (`e_id`, `e_name`, `e_salary`) VALUES
	(1, '侯胖胖', 25000),
	(2, '楊磅磅', 23000),
	(3, '李噸噸', 33000),
	(4, '任肥肥', 35000),
	(5, '喬豆豆', 32000),
	(6, '任天蓬', 38000),
	(7, '任政富', 40000);

resources 下的 jdbc.properties 配置 mysql 數據庫的連接信息

mysql.driver=com.mysql.cj.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/testdb?useSSL=false
mysql.username=root
mysql.password=123456

在 config 目錄下,JdbcConfig 類是采用 druid 數據庫連接池配置數據庫連接的類,具體內容如下:

package com.jobs.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;

//這里采用 @PropertySource 注解,獲取 resources 目錄下的 jdbc.properties 文件內容
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {

    //這里采用 @Value 注解獲取 jdbc.properties 文件中每個 key 的配置值
    @Value("${mysql.driver}")
    private String driver;

    @Value("${mysql.url}")
    private String url;

    @Value("${mysql.username}")
    private String userName;

    @Value("${mysql.password}")
    private String password;

    //這里采用 @Bean 注解,表明該方法返回連接數據庫的數據源對象
    //由於我們只有這一個數據源,因此不需要使用 BeanId 進行標識
    @Bean
    public DataSource getDataSource(){
        //采用阿里巴巴的 druid 數據庫連接池的數據源
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }
}

在 Config 目錄下 MybatisConfig 類用來配置 SqlSession 工廠對象和定義的訪問數據庫的 Mapper 接口,具體內容如下:

package com.jobs.config;

import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.Configuration;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

public class MybatisConfig {

    //這里的 Bean 由 Spring 根據類型自動調用,因此不需要指定 BeanId
    //使用 @Autowired 注解,Spring 自動根據類型將上面的 druid 的數據源賦值到這里
    @Bean
    public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        //這里配置,將 com.jobs.domain 下的所有 JavaBean 實體類的名字作為別名
        //這樣 MyBatis 中可以直接使用類名,而不需要使用完全限定名
        ssfb.setTypeAliasesPackage("com.jobs.domain");
        ssfb.setDataSource(dataSource);

        //這里配置,讓 MyBatis 在運行時,控制台打印 sql 語句,方便排查問題
        Configuration mybatisConfig = new Configuration();
        mybatisConfig.setLogImpl(StdOutImpl.class);
        ssfb.setConfiguration(mybatisConfig);

        return ssfb;
    }

    //配置 MyBatis 使用 com.jobs.dao 下所有的接口,生成訪問數據庫的代理類
    @Bean
    public MapperScannerConfigurer getMapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.jobs.dao");
        return msc;
    }
}

在 Config 目錄下,SpringConfig 類即為 Spring 的配置類,其將 JdbcConfig 類和 MybatisConfig 類整合在一起。

需要注意的是 SpringConfig 類最好采用 @Import 注解導入其它配置類,不要將相關的配置方法寫到這里,否則會出現無法獲取配置信息的問題。

package com.jobs.config;

import org.springframework.context.annotation.*;

//@Configuration 這個注解,表明此類是 Spring 的配置類
@Configuration
@ComponentScan("com.jobs")
@Import({JdbcConfig.class, MybatisConfig.class})
public class SpringConfig {
}

三、Spring 采用 MyBatis 操作數據庫細節

有了上面 Spring 對 MyBatis 的整合配置,下面 Spring 采用 MyBatis 操作數據庫就很簡單了,開發人員根本不需要編寫訪問數據庫的任何代碼,只需要定義訪問數據庫的接口,配置接口所需要采用的 SQL 語句即可,大大提高了開發效率。

Employee 實體類的具體內容如下:

package com.jobs.domain;

//注意:為了貼合實際開發場景,這里故意定義的【對象字段名稱】與【數據庫表字段名稱】完全不相同
//在下面的 Dao 接口中,采用 @Results 注解,建立【對象字段名稱】與【數據庫表字段名稱】的對應關系
public class Employee {

    private Integer id;
    private String name;
    private Integer salary;

    //建議空參構造函數,必須要有,因為項目中的各種框架一般都會使用
    public Employee() {
    }

    public Employee(Integer id, String name, Integer salary) {
        this.id = id;
        this.name = name;
        this.salary = salary;
    }

    //這里省去了 3 個字段的 get 和 set 方法.....

    //重寫 toString 方法,目的在於能夠把實體對象打印出來,方便查看
    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", salary=" + salary +
                '}';
    }
}

在 EmployeeDao 類中,定義訪問數據庫的方法,MyBatis 會自動根據其定義的接口生成代理類。

這里采用注解的方式直接在 EmployeeDao 類中定義的各個方法上編寫 SQL 語句,對於比較復雜的 SQL 語句,可以采用額外的方法進行返回,具體內容如下:

package com.jobs.dao;

import com.jobs.domain.Employee;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface EmployeeDao {

    //添加員工
    @ResultMap("employee_map")
    @Insert("insert into employee(e_id,e_name,e_salary) values(#{id},#{name},#{salary})")
    Integer add(Employee emp);

    //修改員工信息
    @ResultMap("employee_map")
    @Update("update employee set e_name=#{name},e_salary=#{salary} where e_id=#{id}")
    Integer update(Employee emp);

    //查詢出所有員工,按照id升序排列
    //在 select 方法上定義 employee_map
    //建立 Employee 實體類的屬性與數據庫表 employee 的字段對應關系
    @Results(id = "employee_map", value = {
            @Result(column = "e_id", property = "id"),
            @Result(column = "e_name", property = "name"),
            @Result(column = "e_salary", property = "salary")})
    @Select("select e_id,e_name,e_salary from employee order by e_id")
    List<Employee> selectAll();

    //根據條件,查詢員工
    //這里故意讓傳遞的參數名稱不一樣,只要使用 @Parm 進行規范即可
    @ResultMap("employee_map")
    @SelectProvider(type = EmployeeSQL.class, method = "getSelectByConditionSQL")
    List<Employee> selectByCondition(@Param("ename") String aaa,
                                     @Param("salaryStart") Integer bbb,
                                     @Param("salaryEnd") Integer ccc);

    //傳入一個或多個id,刪除指定的員工
    //這里故意讓傳遞的參數名稱不一樣,只要使用 @Parm 進行規范即可
    @DeleteProvider(type = EmployeeSQL.class, method = "getDeleteByIdsSQL")
    Integer deleteByIds(Integer... empids);
}

注意:為了貼合實際開發場景,上面故意將接口中最后 2 個方法的 SQL 編寫難度提升,這樣就不能直接在接口方法中編寫 SQL 語句,必須通過其它類的方法獲取相關的 SQL 語句。

對於從其它類的方法獲取相關的 SQL 語句的方法,為了貼合實際開發場景,這里故意定義【接口方法的參數名稱】與【獲取SQL語句的方法的參數名稱】完全不一樣,從而可以在兩個方法上同時使用 @Param 注解,建議參數傳遞的對應關系。

最后 2 個方法獲取相關 SQL 語句的 EmployeeSQL 類內容如下:

package com.jobs.dao;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Param;

public class EmployeeSQL {

    //這里故意讓傳遞的參數名稱不一樣,只要使用 @Parm 進行規范即可
    //盡量使用 @Parm 中的參數名稱,這樣可以防止 SQL 注入攻擊
    public String getSelectByConditionSQL(@Param("ename") String xxx,
                                          @Param("salaryStart") Integer yyy,
                                          @Param("salaryEnd") Integer zzz) {
        StringBuilder sql = new StringBuilder();
        sql.append(" select e_id,e_name,e_salary from employee where 1=1");

        if (StringUtils.isNotBlank(xxx)) {
            sql.append(" and (e_name like CONCAT('%',#{ename},'%'))");
        }
        if (yyy != null) {
            sql.append(" and e_salary >= #{salaryStart}");
        }
        if (zzz != null) {
            sql.append(" and e_salary <= #{salaryEnd}");
        }

        /*if (StringUtils.isNotBlank(xxx)) {
            sql.append(" and (e_name like '%" + xxx + "%')");
        }
        if (yyy != null) {
            sql.append(" and e_salary >=" + yyy);
        }
        if (zzz != null) {
            sql.append(" and e_salary <=" + zzz);
        }*/

        sql.append(" order by e_id");
        return sql.toString();
    }

    //這里故意讓傳遞的參數名稱不一樣
    //如果不需要防止 sql 注入攻擊的話,使用普通參數即可
    public String getDeleteByIdsSQL(Integer... ppp) {
        StringBuilder sql = new StringBuilder();
        sql.append("delete from employee");

        if (ppp != null && ppp.length > 0) {
            String idsql = StringUtils.join(ppp, ",");
            sql.append(" where e_id in (").append(idsql).append(")");
        } else {
            sql.append(" where 1=2");
        }

        return sql.toString();
    }
}

可以發現上面根本沒有編寫訪問數據庫的具體代碼,只是定義接口,以及在接口上定義 SQL 語句即可。

然后我們編寫 EmployeeService 類中對應的方法,調用接口中的相關方法,實現對數據庫的訪問。實際場景中 Service 中的一個方法,有可能根據業務需要調用好多 Dao 層中的方法,這里沒有什么復雜的業務,所以就每個 Service 方法調用相應的 Dao 方法了,具體內容如下:

package com.jobs.service;

import com.jobs.dao.EmployeeDao;
import com.jobs.domain.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

//采用 @Service 注解將 EmployeeService 的實例化對象添加到 Spring 容器中
//默認是單例模式
@Service("employeeService")
public class EmployeeService {

    //采用 @Autowired 將 MyBatis 根據接口動態生成的代理對象注入到這里
    @Autowired
    private EmployeeDao employeeDao;

    public Integer add(Employee emp) {
        return employeeDao.add(emp);
    }

    public Integer update(Employee emp) {
        return employeeDao.update(emp);
    }

    public List<Employee> selectAll() {
        return employeeDao.selectAll();
    }

    public List<Employee> selectByCondition(String name, Integer start, Integer end) {
        return employeeDao.selectByCondition(name, start, end);
    }

    public Integer deleteByIds(Integer... eids) {
        return employeeDao.deleteByIds(eids);
    }
}

最后我們編寫 EmployeeApp 類中的 main 方法,測試一下 Spring 采用純注解的方式整合 MyBatis 的成果。

package com.jobs;

import com.jobs.config.SpringConfig;
import com.jobs.domain.Employee;
import com.jobs.service.EmployeeService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.List;

public class EmployeeApp {
    public static void main(String[] args) {
        //通過注解配置類獲取 Spring 容器
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        //從 Spring 容器中獲取 EmployeeService 對象
        EmployeeService empService = (EmployeeService) ctx.getBean("employeeService");
        //調用其獲取所有員工的方法,並打印獲取的結果
        List<Employee> emplist = empService.selectAll();
        for (Employee emp : emplist) {
            System.out.println(emp);
        }
    }
}

執行后的結果如下圖所示:

image


四、Spring 整合 Junit 相關細節

Spring 工程搭建完畢后,想要測試其中的一些方法,確實不太方便,比如本 demo 就必須得在 EmployeeApp 中的 main 方法中進行編碼測試。為了能夠更好的編寫測試方法,我們就必須讓 Spring 整合 Junit ,這樣就能夠在獨立的測試類中,通過 Spring 容器注入相關的對象,從而測試對象的方法調用。

Spring 整合 Junit 非常簡單,主要就兩步:導入 2 個 jar 包 和 使用 2 個注解。

第一步:導入 2 個 jar 包。

在本篇博客的最開始,已經在 pom.xml 文件中導入 2 個相應的 jar 包,具體內容為:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.17</version>
    <scope>test</scope>
</dependency>

第二步:使用 2 個注解。

@RunWith(SpringJUnit4ClassRunner.class) :這個是固定寫法,這里不做過多解釋。

@ContextConfiguration(classes = Spring 的配置類) :指定 Spring 的配置類。
在本 demo 中 Spring 的配置類是 SpringConfig 類。

根據上面 2 個步驟,我們編寫 Spring 整合 Junit 的測試類 EmployeeTest,具體內容如下:

package com.jobs;

import com.jobs.config.SpringConfig;
import com.jobs.domain.Employee;
import com.jobs.service.EmployeeService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class EmployeeTest {

    //由於 Spring 已經整合了 Junit
    //因此這里可以直接使用 @Autowired 注解根據類型注入 EmployeeService 對象
    @Autowired
    private EmployeeService employeeService;

    //添加三個新員工信息
    @Test
    public void add() {
        Employee emp1 = new Employee(100, "候菲特", 50000);
        Integer result1 = employeeService.add(emp1);
        System.out.println(result1);

        Employee emp2 = new Employee(101, "任蓋茨", 60000);
        Integer result2 = employeeService.add(emp2);
        System.out.println(result2);

        Employee emp3 = new Employee(102, "李政富", 70000);
        Integer result3 = employeeService.add(emp3);
        System.out.println(result3);
    }

    //修改一名員工的信息
    @Test
    public void update() {
        Employee emp = new Employee(100, "任首富", 80000);
        Integer result = employeeService.update(emp);
        System.out.println(result);
    }

    //查詢所有員工
    @Test
    public void selectAll() {
        List<Employee> emplist = employeeService.selectAll();
        System.out.println(emplist);
    }

    //根據相關條件查詢篩選員工
    @Test
    public void selectByCondition() {
        String name = "任";
        int start = 30000;
        int end = 60000;
        List<Employee> emplist = employeeService.selectByCondition(name, start, end);
        System.out.println(emplist);
    }

    //刪除指定id的一個或多個員工
    @Test
    public void deleteByIds() {
        Integer result = employeeService.deleteByIds(100, 101, 102);
        System.out.println(result);
    }
}

到此為止,已經完成了 Spring 采用純注解的方式整合 MyBatis 和 Junit,並且在方法執行過程中能夠打印出所執行的 SQL 語句,方便查看和排查問題。

本 demo 貼合實際開發,故意制造了一些麻煩,並進行解決,Demo 代碼經過測試沒有任何問題,方便大家參考。

最后提供本 Demo 的源代碼:https://files.cnblogs.com/files/blogs/699532/spring_mybatis_junit.zip




免責聲明!

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



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