Mybatis单元测试,不使用spring

这篇具有很好参考价值的文章主要介绍了Mybatis单元测试,不使用spring。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

平时开发过程中需要对mybatis的Mapper类做单元测试,主要是验证语法是否正确,尤其是一些复杂的动态sql,一般项目都集成了spring或springboot,当项比较大时,每次单元测试启动相当慢,可能需要好几分钟,因此写了一个纯mybatis的单元测试基类,实现单元测试的秒级启动。

单元测试基类MybatisBaseTest类主要完成如下工作:

1.加载mybatis配置文件
在MybatisBaseTest.init()方法实现,
该动作在整个单元测试生命周期只执行一次,并且在启动前执行 ,
因此使用junit的@BeforeClass注解标注,表示该动作在单元测试启动前执行。

2.打开session
在MybatisBaseTest.openSession()方法实现,
该方法获取一个mybatis的SqlSession,并将SqlSession存入到线程本地变量中,
使用junit的@Before注解标注,表示在每一个单元测试方法运行前都执行该动作。

3.mapper对象注入
单元测试子类中通过在字段上使用@javax.annotation.Resource注解自动注入Mapper对象,子类测试方法中可以直接使用mapper对象做测试。

4.关闭session
在MybatisBaseTest.closeSession()方法实现,
从线程本地变量中获取SqlSession对象,完成事务的回滚(单元测试一般不提交事务)和connection的关闭等逻辑。
使用junit的@After注解标注,表示该动作在每一个单元测试方法运行完成后执行。

源码地址: mybatis测试基类

整体包结构如下:
Mybatis单元测试,不使用spring,Mybatis,mybatis,单元测试

需要的Maven依赖如下

<!-- mybatis依赖 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.5</version>
</dependency>
<!-- 单元测试junit包 -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13</version>
</dependency>
<!-- 用到spring的FileSystemXmlApplicationContext工具类来加载配置 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>

MybatisBasetTest类的代码如下:

package com.zhouyong.practice.mybatis.base;

import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * mybatis单元测试基类
 * @author zhouyong
 * @date 2023/7/23 9:45 上午
 */
public class MybatisBaseTest {

    private final static String configLocation = "mybatis/mybatis-config-test.xml";

    private static ThreadLocal<LocalSession> sessionThreadLocal;

    private static List<LocalSession> sessionPool;

    private static SqlSessionFactory sqlSessionFactory;


    /**
     * 单元类测试启动前的初始化动作
     * 初始化数据库session等相关信息
     */
    @BeforeClass
    public final static void init() throws IOException {

        //多个单元测试类批量执行时,init方法会重复执行,因此做空判断避免重复执行
        if(sqlSessionFactory!=null){
            return ;
        }

        //解析mybatis全局配置文件
        Configuration configuration = parseConfiguration();
        //解析mapper配置
        parseMapperXmlResource(configuration);
        //创建SqlSessionFactory
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

        //用于存储所有的session
        sessionPool = new ArrayList<>();
        //LocalSession的线程本地变量
        sessionThreadLocal = new ThreadLocal<>();
        //保底操作,确保异常退出时关闭所有数据库连接
        Runtime.getRuntime().addShutdownHook(new Thread(()->closeAllSession()));
    }

    /**
     * 启动session并且注入mapper对象
     * 每一个单元测试方法启动之前会自动执行该方法
     * 如果子类也有@Before方法,父类的@Before方法先于子类执行
     */
    @Before
    public final void openSessionAndInjectMapper(){
        LocalSession localSession = createLocalSession();
        sessionThreadLocal.set(localSession);
        sessionPool.add(localSession);
        injectMapper();
    }

    /**
     * mapper代理对象注入
     */
    private void injectMapper(){
        Class<? extends MybatisBaseTest> testClass = this.getClass();
        Field[] fields = testClass.getDeclaredFields();
        for (Field field : fields) {
            if(field.getAnnotation(javax.annotation.Resource.class)!=null){
                boolean accessible = field.isAccessible();
                try {
                    if(!accessible){
                        field.setAccessible(true);
                    }
                    Object mapperObj = sessionThreadLocal.get().getMapper(field.getType());
                    field.set(this,mapperObj);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("mapper对象注入失败:"+field.getName(),e);
                }finally {
                    field.setAccessible(accessible);
                }
            }
        }

    }

    /**
     * 关闭session
     * 每一个单元测试执行完之后都会自动执行该方法
     * 如果子类也有@After方法,则子类的@After方法先于父类执行(于@Before方法相反)
     */
    @After
    public final void closeSession(){
        LocalSession localSession = sessionThreadLocal.get();
        if(localSession!=null){
            localSession.close();
            sessionPool.remove(localSession);
            sessionThreadLocal.remove();
        }
    }

    /**
     * 保底操作,异常退出时关闭所有session
     */
    public final static void closeAllSession(){
        if(sessionPool!=null){
            for (LocalSession localSession : sessionPool) {
                localSession.close();
            }
            sessionPool.clear();
            sessionPool = null;
        }
        sessionThreadLocal = null;
    }

    /**
     * 解析mybatis全局配置文件
     * @throws IOException
     */
    private final static Configuration parseConfiguration() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream(configLocation);
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream);
        Configuration configuration = parser.parse();

        //驼峰命名自动转换
        configuration.setMapUnderscoreToCamelCase(true);

        Properties properties = configuration.getVariables();
        //如果密码有加密,则此处可以进行解密
        //String pwd = properties.getProperty("jdbcPassword");
        //((PooledDataSource)configuration.getEnvironment().getDataSource()).setPassword("解密后的密码");

        return configuration;
    }

    /**
     * 解析mapper配置文件
     * @throws IOException
     */
    private final static void parseMapperXmlResource(Configuration configuration) throws IOException {
        String[] mapperLocations = configuration.getVariables().getProperty("mapperLocations").split(",");
        //借助spring的FileSystemXmlApplicationContext工具类,根据配置匹配解析出所有路径
        FileSystemXmlApplicationContext xmlContext = new FileSystemXmlApplicationContext();

        for (String mapperLocation : mapperLocations) {
            Resource[] mapperResources = xmlContext.getResources(mapperLocation);
            for (Resource mapperRes : mapperResources) {
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperRes.getInputStream(),
                        configuration,
                        mapperRes.toString(),
                        configuration.getSqlFragments());
                xmlMapperBuilder.parse();
            }

        }
    }

    /**
     * 创建自定义的LocalSession
     * @return
     */
    private final LocalSession createLocalSession(){
        try{
            String isCommitStr = sqlSessionFactory.getConfiguration().getVariables().getProperty("isCommit");
            boolean isCommit = StringUtils.isEmpty(isCommitStr) ? false : Boolean.parseBoolean(isCommitStr);

            SqlSession sqlSession = sqlSessionFactory.openSession(false);
            Connection connection = sqlSession.getConnection();
            connection.setAutoCommit(false);

            return new LocalSession(sqlSession, connection, isCommit);
        }catch (SQLException e){
            throw new RuntimeException(e);
        }
    }

}

LocalSession类对SqlSession做了一层封装

package com.zhouyong.practice.mybatis.base;

import org.apache.ibatis.session.SqlSession;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * @author zhouyong
 * @date 2023/7/23 9:52 上午
 */
public class LocalSession {

    /** mybatis 的 session */
    private SqlSession session;

    /** sql 的 connection */
    private Connection connection;

    /** 是否提交事物,单元测试一般不需要提交事物(直接回滚) */
    private boolean isCommit;

    public LocalSession(SqlSession session, Connection connection, boolean isCommit) throws SQLException {
        this.isCommit = isCommit;
        this.session = session;
        this.connection = connection;
    }

    /**
     * 获取mapper对象
     * @param mapperClass
     * @param <T>
     * @return
     */
    public <T> T getMapper(Class<T> mapperClass){
        return session.getMapper(mapperClass);
    }

    /**
     * 关闭session
     * @throws SQLException
     */
    public void close(){
        try{
            if(isCommit){
                connection.commit();
            }else{
                connection.rollback();
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            try{
                session.close();
            }catch (Exception e) {
                e.printStackTrace();
            }/*finally {
                try {
                    if(!connection.isClosed()){
                        connection.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }*/
        }
    }

}

mybatis-config-test.xml配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <properties resource="mybatis/mybatis-db-test.properties"></properties>

    <settings>
        <!-- 打印查询语句 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!-- 控制全局缓存(二级缓存)-->
        <setting name="cacheEnabled" value="false"/>
        <!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载,增加启动效率。默认 false  -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过select标签的 fetchType来覆盖-->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

</configuration>

mybatis-db-test.properties配置文件

#扫描mapper.xml的路径,多个用英文逗号隔开
mapperLocations=classpath:mapper/*.xml

#是否提交事务,单元测试一般不提交设置为false即可
isCommit=false

#数据库连接参数配置
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mysql?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
jdbc.username=root
jdbc.password=123456

测试类CustomerMapperTest继承MybatisBaseTest

package com.zhouyong.practice.mybatis.test;

import com.zhouyong.practice.mybatis.base.MybatisBaseTest;
import org.junit.Test;

import javax.annotation.Resource;
import java.util.List;

/**
 *
 * @author zhouyong
 * @date 2023/7/23 12:32 下午
 */
public class CustomerMapperTest extends MybatisBaseTest {

    /**
     * 支持自动注入Mapper对象
     */
    @Resource
    private CustomerMapper customerMapper;

    @Test
    public void test1(){
        List<CustomerEntity> list = customerMapper.selectAll();
        System.out.println("1 list.size()=="+list.size());

        CustomerEntity entity = new CustomerEntity();
        entity.setName("李四");
        entity.setAge(55);
        entity.setSex("男");

        customerMapper.insertMetrics(entity);

        list = customerMapper.selectAll();
        System.out.println("2 list.size()=="+list.size());
    }

    @Test
    public void test2(){
        List<CustomerEntity> metricsEntities = customerMapper.selectAll();
        System.out.println("3 list.size()=="+metricsEntities.size());

        CustomerEntity entity = new CustomerEntity();
        entity.setName("王五");
        entity.setAge(55);
        entity.setSex("男");

        customerMapper.insertMetrics(entity);

        metricsEntities = customerMapper.selectAll();
        System.out.println("4 list.size()=="+metricsEntities.size());
    }
}

测试结果符合预期,运行完成后没有提交事务(因为配置中的isCommit设置为false),且单元测试运行完之后所有的connection都已释放。文章来源地址https://www.toymoban.com/news/detail-603237.html

到了这里,关于Mybatis单元测试,不使用spring的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • junit单元测试 mybatis 单独测试 springboot

    目录 省流: 正文 一、直接测(无需配置扫描和xml) 1. 场景 2. 无需配置扫描,直接在测试类注入Mapper 3. 报错 补充: 关于@RunWith 常见的报错: 1.包名不同导致报错 test目录下配置文件:application.yml中配置数据库信息。 test目录下各类:直接写测试类即可,直接调用main目录下各

    2024年02月07日
    浏览(41)
  • 单元测试实战(四)MyBatis-Plus 的测试

    为鼓励单元测试,特分门别类示例各种组件的测试代码并进行解说,供开发人员参考。 本文中的测试均基于JUnit5。 单元测试实战(一)Controller 的测试 单元测试实战(二)Service 的测试     单元测试实战(三)JPA 的测试 单元测试实战(四)MyBatis-Plus 的测试 单元测试实战(

    2024年01月15日
    浏览(30)
  • 【 SpringBoot单元测试 和 Mybatis 增,删,改 操作 】

    单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。 在Java中单元测试的最小单元是类 单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。执行单元测试,就是为了证明这段代码的行为和我们期望是否一致

    2023年04月23日
    浏览(22)
  • Springboot优雅单元测试之mapper的测试(基于mybatis-plus)

    基于springboot的工程,正常单元测试,可以利用IDEA的goto功能自动生成对应的测试类(测试方法),然后在生成的测试类加注解@SpringBootTest,执行对应的test方法即可。但是这样默认是会启动整个springboot应用的,如果有web,还会启动web容器。这个时间比较久, 不够优雅 。 直接撸

    2024年02月11日
    浏览(32)
  • SpringBoot——Service单元测试(包含mybatis、mapper、私有方法等)

      在写单元测试时,免不了遇到私有方法、数据库等一些操作,此时就需要一些mock处理。

    2024年02月13日
    浏览(30)
  • Mybatis-plus多数据源单元测试@MybatisPlusTest

    mybatis-plus多数据源单元测试报错 错误原因分析 多数据源,但是不能取到数据信息 解决方案 在注解中添加 @ImportAutoConfiguration(value = {RmasDataSourceConfig.class}, exclude = DataSourceAutoConfiguration.class) 注意事项 1.@Test添加以后,没有启动键,后来发现引入的包不对,必须引入 org.junit.jup

    2024年02月11日
    浏览(32)
  • SpringBoot -02 SpringBoot整合Mybatis、Druid数据源、单元测试、JSP

    mybatis起步依赖 mysql启动依赖 数据源配置 mybatis相关配置 加载mybatis配置文件 mybatis别名配置 日志配置 加载mapper映射文件配置文件 http://localhost:8080/users Druid连接池后台监控:http://localhost:8080/druid/index.html SpringBoot对JSP的支持不是很友好,所以JSP很少被使用 springboot 不支持jsp 但是

    2024年02月06日
    浏览(79)
  • 针对mockito框架在单元测试中出现Mybatis-Plus链式调用的解决方案

    1、 调用其他service层方法 2、 调用本service层方法 3、其他问题

    2024年01月24日
    浏览(33)
  • SSM整合-Spring整合SringMVC、Mybatis,ssm测试

    ​ SSM(Spring + SpringMVC + Mybatis) 整合,就是三个框架协同开发。 Spring 整合 Mybatis,就是将 Mybatis 核心配置分拣当中数据源的配置、事务管理、工厂的配置、Mapper接口的实现类等 交给Spring进行管理。 mybatisConfig.xml配置信息 整合到 SpringConfig.xml Spring 整合 SpringMVC,就是在web.xml当中

    2023年04月22日
    浏览(40)
  • Spring Boot+Mybatis实现增删改查接口开发+测试(超详细建议收藏)

    Java也是测试必知必会的内容,特别是现在类似spring boot 等Java框架更是成为主流。之前实现的图书增删改查是用Python实现的,没看过的请移步:Flask+mysql 实现增删改查接口开发+测试(图文教程附源码),本次给大家带来用Java实现的后端接口版本,并根据之前的项目总结有做一

    2024年02月03日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包