参考
从头到尾手把手教你搭建阅读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 测试类