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



posted @ 2022-03-20 20:25  乔京飞  阅读(10148)  评论(0编辑  收藏  举报