参考
从头到尾手把手教你搭建阅读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*/@Overridepublic 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 idMetaObject 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:OFFif (sqlCost > 2000) {warning = "[耗时过长]";}// CHECKSTYLE:ON// 开始输出 sqllog.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*/@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}/*** setProperties.** @param properties properties*/@Overridepublic 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 源码 DefaultParameterHandlerif (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 mapperPUBLIC "-//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 configurationPUBLIC "-//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;@BeforeClasspublic 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);}}@Testpublic 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 测试类