mybatis 源码环境搭建

参考

从头到尾手把手教你搭建阅读Mybatis源码的环境(程序员必备技能)
Cannot enable lazy loading because Javassist is not available. Add Javassist to your classpath.

下载源码

https://github.com/mybatis/mybatis-3/

https://github.com/mybatis/parent/tree/mybatis-parent-32
https://github.com/mybatis/mybatis-3/tree/mybatis-3.5.6

新建一个maven 项目

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.laolang.bzrj.mybatis</groupId>
    <artifactId>mybatis-3-source</artifactId>
    <version>1.0</version>
    <packaging>pom</packaging>

    <repositories>
        <repository>
            <id>public</id>
            <name>aliyun nexus</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>public</id>
            <name>aliyun nexus</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
</project>

mybatis-parent

下载代码: https://github.com/mybatis/parent/tree/mybatis-parent-32 , 复制到项目目录,并重命名为: mybatis-parent

修改 mybatis-parent/pom.xml,主要是修改了版本号,用来和本地 mybatis 区别开来

    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-parent</artifactId>
    <version>32-laolang</version>
    <packaging>pom</packaging>

mybatis

下载代码: https://github.com/mybatis/mybatis-3/tree/mybatis-3.5.6 , 复制到项目目录,并重命名为: mybatis

修改 mybatis/pom.xml,主要是修改了版本号,用来和本地 mybatis 区别开来

    <parent>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-parent</artifactId>
        <version>32-laolang</version>
        <relativePath>../mybatis-parent/pom.xml</relativePath>
    </parent>

    <artifactId>mybatis</artifactId>
    <version>3.5.6-laolang</version>
    <packaging>jar</packaging>

添加 module

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.laolang.bzrj.mybatis</groupId>
        <artifactId>mybatis-3-source</artifactId>
        <version>1.0</version>
    </parent>

    <artifactId>mybatis-source</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>

        <testng.version>6.14.3</testng.version>

        <mybatis.version>3.5.6-laolang</mybatis.version>
        <pagehelper.version>5.3.2</pagehelper.version>
        <mysql-connector-java.version>8.0.22</mysql-connector-java.version>

        <logback.version>1.2.12</logback.version>

        <hutool.version>5.8.11</hutool.version>
        <vavr.version>0.10.4</vavr.version>
        <mapstruct.version>1.4.2.Final</mapstruct.version>
        <guava.version>23.0</guava.version>
        <lombok.versino>1.18.16</lombok.versino>
        <commons-lang3.version>3.14.0</commons-lang3.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>${testng.version}</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
		<!-- 
			下面这两个依赖必须添加,否则会报错: 
			Cannot enable lazy loading because Javassist is not available. Add Javassist to your classpath.
			参考: https://blog.csdn.net/weixin_45056780/article/details/113449245
		-->
        <dependency>
            <groupId>ognl</groupId>
            <artifactId>ognl</artifactId>
            <version>3.2.15</version>
            <scope>compile</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.27.0-GA</version>
            <scope>compile</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>${pagehelper.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql-connector-java.version}</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>
        <dependency>
            <groupId>io.vavr</groupId>
            <artifactId>vavr</artifactId>
            <version>${vavr.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${mapstruct.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-jdk8</artifactId>
            <version>${mapstruct.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>${mapstruct.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.versino}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons-lang3.version}</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <testResources>
            <testResource>
                <directory>${project.build.testSourceDirectory}</directory>
                <excludes>
                    <exclude>**/*.java</exclude>
                </excludes>
            </testResource>
        </testResources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-compiler-plugin.version}</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

db.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_source?serverTimezone=UTC
jdbc.username=root
jdbc.password=root

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
    scan: 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
    scanPeriod: 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
    debug: 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false
-->
<configuration scan="true" scanPeriod="60 seconds" debug="false">

    <!-- 输出到控制台 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 输出到滚动文件 -->
    <appender name="rolling-file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>../logs/mybatis-base/app.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>../logs/mybatis-base/%d{yyyy-MM-dd}/app-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- 日志文件保留天数 -->
            <maxHistory>150</maxHistory>
        </rollingPolicy>
    </appender>

    <root level="DEBUG" />

    <logger name="org.apache.ibatis" level="DEBUG" additivity="false">
        <appender-ref ref="console" />
        <appender-ref ref="rolling-file" />
    </logger>

    <logger name="com.laolang" level="DEBUG" additivity="false">
        <appender-ref ref="console" />
        <appender-ref ref="rolling-file" />
    </logger>
</configuration>

拦截器

package com.laolang.bzrj.mybatis.interceptor;

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

@Slf4j
@Intercepts({
        @Signature(type = StatementHandler.class, method = "query", args = {Statement.class,
                ResultHandler.class}),
        @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
        @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
public class MybatisPrintSqlInterceptor implements Interceptor {

    /**
     * mybatis 配置对象.
     */
    private Configuration configuration = null;

    /**
     * 时间格式化.
     */
    private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(
            () -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"));

    /**
     * 拦截器主方法.
     *
     * @param invocation invocation
     * @return sql 执行结果
     * @throws Throwable Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();
        long startTime = System.currentTimeMillis();
        Object result = null;
        try {
            result = invocation.proceed();
            return result;
        } finally {
            try {
                long endTime = System.currentTimeMillis();
                long sqlCost = endTime - startTime;
                StatementHandler statementHandler = (StatementHandler) target;
                BoundSql boundSql = statementHandler.getBoundSql();
                if (configuration == null) {
                    // final DefaultParameterHandler parameterHandler = (DefaultParameterHandler) statementHandler.getParameterHandler();
                    final ParameterHandler parameterHandler = statementHandler.getParameterHandler();
                    // Field configurationField = ReflectionUtils.findField(
                    //         parameterHandler.getClass(), "configuration");
                    // ReflectionUtils.makeAccessible(configurationField);
                    Field configurationField = ReflectUtil.getField(parameterHandler.getClass(), "configuration");
                    ReflectUtil.setAccessible(configurationField);
                    this.configuration = (Configuration) configurationField.get(parameterHandler);
                }

                // 输出 mapper id
                MetaObject metaObject = SystemMetaObject.forObject(target);
                MappedStatement ms = (MappedStatement) metaObject.getValue(
                        "delegate.mappedStatement");
                String id = ms.getId();

                // 替换参数格式化Sql语句,去除换行符
                String sql = formatSql(boundSql, configuration).concat(";");
                String warning = "";
                // CHECKSTYLE:OFF
                if (sqlCost > 2000) {
                    warning = "[耗时过长]";
                }
                // CHECKSTYLE:ON

                // 开始输出 sql
                log.info("map-id: {}", id);
                log.info("[ {} ] [ {} ] ms {}", sql, sqlCost, warning);

                if (result instanceof List) {
                    log.info("Total: {}", ((List) result).size());
                } else {
                    log.info("Updates: {}", result);
                }
            } catch (Exception e) {
                log.error("==> 打印sql 日志异常 {0}", e);
            }
        }
    }

    /**
     * plugin.
     *
     * @param target target
     * @return Object
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * setProperties.
     *
     * @param properties properties
     */
    @Override
    public void setProperties(Properties properties) {

    }

    /**
     * 获取完整的sql实体的信息.
     *
     * @param boundSql      boundSql
     * @param configuration configuration
     * @return 格式化后的 sql
     */
    private String formatSql(BoundSql boundSql, Configuration configuration) {
        String sql = boundSql.getSql();

        Object parameterObject = boundSql.getParameterObject();
        // 输入sql字符串空判断
        if (StrUtil.isBlank(sql)) {
            return "";
        }
        if (configuration == null) {
            return "";
        }
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        sql = beautifySql(sql);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        // 参考mybatis 源码 DefaultParameterHandler
        if (parameterMappings != null) {
            for (ParameterMapping parameterMapping : parameterMappings) {
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else {
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    String paramValueStr = "";
                    if (value instanceof String) {
                        paramValueStr = "'" + value + "'";
                    } else if (value instanceof Date) {
                        paramValueStr = "'" + DATE_FORMAT_THREAD_LOCAL.get().format(value) + "'";
                    } else {
                        paramValueStr = value + "";
                    }
                    sql = sql.replaceFirst("\\?", paramValueStr);
                }
            }
        }
        return sql;
    }

    /**
     * 美化 sql.
     *
     * @param sql sql
     * @return sql
     */
    private String beautifySql(String sql) {
        sql = sql.replaceAll("[\\s\n ]+", " ");
        return sql;
    }
}

实体类

package com.laolang.bzrj.mybatis.hello;

import java.time.LocalDateTime;
import lombok.Data;

@Data
public class SysDictType {

    private Long id;
    private String createBy;
    private LocalDateTime createTime;
    private String updateBy;
    private LocalDateTime updateTime;
    private String remark;
    private String name;
    private String type;
    private String groupCode;
    private String status;
}

mapper

package com.laolang.bzrj.mybatis.hello;

import org.apache.ibatis.annotations.Param;

public interface SysDictTypeMapper {


    SysDictType selectById(@Param("id") Long id);
}

mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.laolang.bzrj.mybatis.hello.SysDictTypeMapper">

    <resultMap id="SysDictTypeBaseMap" type="com.laolang.bzrj.mybatis.hello.SysDictType">
        <id property="id" column="id" javaType="java.lang.Long" jdbcType="BIGINT"/>
        <result property="createBy" column="create_by" javaType="java.lang.String" jdbcType="VARCHAR"/>
        <result property="createTime" column="create_time" javaType="java.time.LocalDateTime" jdbcType="TIMESTAMP"/>
        <result property="updateBy" column="update_by" javaType="java.lang.String" jdbcType="VARCHAR"/>
        <result property="updateTime" column="update_time" javaType="java.time.LocalDateTime" jdbcType="TIMESTAMP"/>
        <result property="remark" column="remark" javaType="java.lang.String" jdbcType="VARCHAR"/>
        <result property="name" column="name" javaType="java.lang.String" jdbcType="VARCHAR"/>
        <result property="type" column="type" javaType="java.lang.String" jdbcType="VARCHAR"/>
        <result property="groupCode" column="group_code" javaType="java.lang.String" jdbcType="VARCHAR"/>
        <result property="status" column="status" javaType="java.lang.String" jdbcType="VARCHAR"/>
    </resultMap>

    <select id="selectById" resultMap="SysDictTypeBaseMap">
        select * from sys_dict_type where id = #{id}
    </select>
</mapper>

mybatis-config.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="db.properties" />

    <settings>
        <setting name="logImpl" value="SLF4J" />
    </settings>

    <plugins>
        <plugin interceptor="com.laolang.bzrj.mybatis.interceptor.MybatisPrintSqlInterceptor" />
        <plugin interceptor="com.github.pagehelper.PageInterceptor" />
    </plugins>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <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>

    <mappers>
        <mapper resource="com/laolang/bzrj/mybatis/hello/SysDictTypeMapper.xml" />
    </mappers>
</configuration>

测试类

package com.laolang.bzrj.mybatis.hello;

import cn.hutool.json.JSONUtil;
import java.io.IOException;
import java.io.Reader;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

@Slf4j
public class HelloTest {

    private SqlSessionFactory sqlSessionFactory;

    @BeforeClass
    public void beforeClass() {
        try (Reader reader = Resources.getResourceAsReader("com/laolang/bzrj/mybatis/hello/mybatis-config.xml")) {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Test
    public void testOne() {
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
            SysDictTypeMapper sysDictTypeMapper = sqlSession.getMapper(SysDictTypeMapper.class);
            SysDictType dictType = sysDictTypeMapper.selectById(1L);
            log.info("dictType:{}", JSONUtil.toJsonStr(dictType));
        }
    }
}

效果

org.apache.ibatis.session.SqlSessionFactoryBuilder 49 行左右打一个断点,然后 debug 测试类

image

posted @ 2024-08-05 11:55  潼关路边的一只野鬼  阅读(17)  评论(0编辑  收藏  举报