0


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测试基类

整体包结构如下:
在这里插入图片描述

需要的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

类的代码如下:

packagecom.zhouyong.practice.mybatis.base;importorg.apache.ibatis.builder.xml.XMLConfigBuilder;importorg.apache.ibatis.builder.xml.XMLMapperBuilder;importorg.apache.ibatis.io.Resources;importorg.apache.ibatis.session.Configuration;importorg.apache.ibatis.session.SqlSession;importorg.apache.ibatis.session.SqlSessionFactory;importorg.apache.ibatis.session.SqlSessionFactoryBuilder;importorg.junit.After;importorg.junit.Before;importorg.junit.BeforeClass;importorg.springframework.context.support.FileSystemXmlApplicationContext;importorg.springframework.core.io.Resource;importorg.springframework.util.StringUtils;importjava.io.IOException;importjava.io.InputStream;importjava.lang.reflect.Field;importjava.sql.Connection;importjava.sql.SQLException;importjava.util.ArrayList;importjava.util.List;importjava.util.Properties;/**
 * mybatis单元测试基类
 * @author zhouyong
 * @date 2023/7/23 9:45 上午
 */publicclassMybatisBaseTest{privatefinalstaticString configLocation ="mybatis/mybatis-config-test.xml";privatestaticThreadLocal<LocalSession> sessionThreadLocal;privatestaticList<LocalSession> sessionPool;privatestaticSqlSessionFactory sqlSessionFactory;/**
     * 单元类测试启动前的初始化动作
     * 初始化数据库session等相关信息
     */@BeforeClasspublicfinalstaticvoidinit()throwsIOException{//多个单元测试类批量执行时,init方法会重复执行,因此做空判断避免重复执行if(sqlSessionFactory!=null){return;}//解析mybatis全局配置文件Configuration configuration =parseConfiguration();//解析mapper配置parseMapperXmlResource(configuration);//创建SqlSessionFactory
        sqlSessionFactory =newSqlSessionFactoryBuilder().build(configuration);//用于存储所有的session
        sessionPool =newArrayList<>();//LocalSession的线程本地变量
        sessionThreadLocal =newThreadLocal<>();//保底操作,确保异常退出时关闭所有数据库连接Runtime.getRuntime().addShutdownHook(newThread(()->closeAllSession()));}/**
     * 启动session并且注入mapper对象
     * 每一个单元测试方法启动之前会自动执行该方法
     * 如果子类也有@Before方法,父类的@Before方法先于子类执行
     */@BeforepublicfinalvoidopenSessionAndInjectMapper(){LocalSession localSession =createLocalSession();
        sessionThreadLocal.set(localSession);
        sessionPool.add(localSession);injectMapper();}/**
     * mapper代理对象注入
     */privatevoidinjectMapper(){Class<?extendsMybatisBaseTest> 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){thrownewRuntimeException("mapper对象注入失败:"+field.getName(),e);}finally{
                    field.setAccessible(accessible);}}}}/**
     * 关闭session
     * 每一个单元测试执行完之后都会自动执行该方法
     * 如果子类也有@After方法,则子类的@After方法先于父类执行(于@Before方法相反)
     */@AfterpublicfinalvoidcloseSession(){LocalSession localSession = sessionThreadLocal.get();if(localSession!=null){
            localSession.close();
            sessionPool.remove(localSession);
            sessionThreadLocal.remove();}}/**
     * 保底操作,异常退出时关闭所有session
     */publicfinalstaticvoidcloseAllSession(){if(sessionPool!=null){for(LocalSession localSession : sessionPool){
                localSession.close();}
            sessionPool.clear();
            sessionPool =null;}
        sessionThreadLocal =null;}/**
     * 解析mybatis全局配置文件
     * @throws IOException
     */privatefinalstaticConfigurationparseConfiguration()throwsIOException{InputStream inputStream =Resources.getResourceAsStream(configLocation);XMLConfigBuilder parser =newXMLConfigBuilder(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
     */privatefinalstaticvoidparseMapperXmlResource(Configuration configuration)throwsIOException{String[] mapperLocations = configuration.getVariables().getProperty("mapperLocations").split(",");//借助spring的FileSystemXmlApplicationContext工具类,根据配置匹配解析出所有路径FileSystemXmlApplicationContext xmlContext =newFileSystemXmlApplicationContext();for(String mapperLocation : mapperLocations){Resource[] mapperResources = xmlContext.getResources(mapperLocation);for(Resource mapperRes : mapperResources){XMLMapperBuilder xmlMapperBuilder =newXMLMapperBuilder(mapperRes.getInputStream(),
                        configuration,
                        mapperRes.toString(),
                        configuration.getSqlFragments());
                xmlMapperBuilder.parse();}}}/**
     * 创建自定义的LocalSession
     * @return
     */privatefinalLocalSessioncreateLocalSession(){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);returnnewLocalSession(sqlSession, connection, isCommit);}catch(SQLException e){thrownewRuntimeException(e);}}}
LocalSession

类对SqlSession做了一层封装

packagecom.zhouyong.practice.mybatis.base;importorg.apache.ibatis.session.SqlSession;importjava.sql.Connection;importjava.sql.SQLException;/**
 * @author zhouyong
 * @date 2023/7/23 9:52 上午
 */publicclassLocalSession{/** mybatis 的 session */privateSqlSession session;/** sql 的 connection */privateConnection connection;/** 是否提交事物,单元测试一般不需要提交事物(直接回滚) */privateboolean isCommit;publicLocalSession(SqlSession session,Connection connection,boolean isCommit)throwsSQLException{this.isCommit = isCommit;this.session = session;this.connection = connection;}/**
     * 获取mapper对象
     * @param mapperClass
     * @param <T>
     * @return
     */public<T>TgetMapper(Class<T> mapperClass){return session.getMapper(mapperClass);}/**
     * 关闭session
     * @throws SQLException
     */publicvoidclose(){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" ?><!DOCTYPEconfigurationPUBLIC"-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><propertiesresource="mybatis/mybatis-db-test.properties"></properties><settings><!-- 打印查询语句 --><settingname="logImpl"value="STDOUT_LOGGING"/><!-- 控制全局缓存(二级缓存)--><settingname="cacheEnabled"value="false"/><!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载,增加启动效率。默认 false  --><settingname="lazyLoadingEnabled"value="true"/><!-- 当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过select标签的 fetchType来覆盖--><settingname="aggressiveLazyLoading"value="false"/></settings><environmentsdefault="development"><environmentid="development"><transactionManagertype="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 --><dataSourcetype="POOLED"><propertyname="driver"value="${jdbc.driver}"/><propertyname="url"value="${jdbc.url}"/><propertyname="username"value="${jdbc.username}"/><propertyname="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

packagecom.zhouyong.practice.mybatis.test;importcom.zhouyong.practice.mybatis.base.MybatisBaseTest;importorg.junit.Test;importjavax.annotation.Resource;importjava.util.List;/**
 *
 * @author zhouyong
 * @date 2023/7/23 12:32 下午
 */publicclassCustomerMapperTestextendsMybatisBaseTest{/**
     * 支持自动注入Mapper对象
     */@ResourceprivateCustomerMapper customerMapper;@Testpublicvoidtest1(){List<CustomerEntity> list = customerMapper.selectAll();System.out.println("1 list.size()=="+list.size());CustomerEntity entity =newCustomerEntity();
        entity.setName("李四");
        entity.setAge(55);
        entity.setSex("男");

        customerMapper.insertMetrics(entity);

        list = customerMapper.selectAll();System.out.println("2 list.size()=="+list.size());}@Testpublicvoidtest2(){List<CustomerEntity> metricsEntities = customerMapper.selectAll();System.out.println("3 list.size()=="+metricsEntities.size());CustomerEntity entity =newCustomerEntity();
        entity.setName("王五");
        entity.setAge(55);
        entity.setSex("男");

        customerMapper.insertMetrics(entity);

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

测试结果符合预期,运行完成后没有提交事务(因为配置中的isCommit设置为false),且单元测试运行完之后所有的connection都已释放。

标签: mybatis 单元测试

本文转载自: https://blog.csdn.net/iteye_19045/article/details/131880261
版权归原作者 薛定谔的雄猫 所有, 如有侵权,请联系我们删除。

“Mybatis单元测试,不使用spring”的评论:

还没有评论