Mybatis 的核心逻辑怎么实现?
类型
、入参
、出参
、条件
)进行解析和处理。怎么通过 Mapper XML 完成对 Mapper 映射器的注册和 SQL 管理?
MapperRegistry
对包路径进行扫描注册映射器,并在 DefaultSqlSession
中进行使用。SqlSessionFactoryBuilder
工厂建造者模式类,通过入口 I/O 的方式对 XML 文件进行解析。Configuration
配置类中。这个配置类会被串联到整个 Mybatis 流程中,所有内容存放和读取都离不开这个类。pom.xml
<dependencies>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.75version>
dependency>
<dependency>
<groupId>org.dom4jgroupId>
<artifactId>dom4jartifactId>
<version>2.1.3version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.7version>
<scope>testscope>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>5.5.0version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.5version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jcl-over-slf4jartifactId>
<version>1.7.5version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.0.9version>
<exclusions>
<exclusion>
<artifactId>slf4j-apiartifactId>
<groupId>org.slf4jgroupId>
exclusion>
exclusions>
dependency>
dependencies>
mybatis-step-03
|-src
|-main
| |-java
| |-com.lino.mybatis
| |-binding
| | |-MapperMethod.java
| | |-MapperProxy.java
| | |-MapperProxyFactory.java
| | |-MapperRegistry.java
| |-builder
| | |-xml
| | | |-XMLConfigBuilder.java
| | |-BaseBuilder.java
| |-io
| | |-Resources.java
| |-mapping
| | |-MappedStatement.java
| | |-SqlCommandType.java
| |-session
| | |-defaults
| | | |-DefaultSqlSession.java
| | | |-DefaultSqlSessionFactory.java
| | |-Configuration.java
| | |-SqlSession.java
| | |-SqlSessionFactory.java
| | |-SqlSessionFactoryBuilder.java
|-test
|-java
| |-com.lino.mybatis.test
| |-dao
| | |-IUserDao.java
| |-po
| | |-User.java
| |-ApiTest.java
|-resources
|-mapper
| |-User_Mapper.xml
|-mybatis-config-datasource.xml
SqlSessionFactoryBuilder
作为整个 mybatis 的入口,提供建造者工厂,包装 XML 解析处理,并返回对应 SqlSessionFactory
处理类。Configuration
配置类中,再通过传递 Configuration
配置类到各个逻辑处理类里,包括 DefaultSqlSession
。Resources.java
package com.lino.mybatis.io;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
/**
* @description: 通过类加载器获得resource的辅助类
*/
public class Resources {
public static Reader getResourceAsReader(String resource) throws IOException {
return new InputStreamReader(getResourceAsStream(resource));
}
private static InputStream getResourceAsStream(String resource) throws IOException {
ClassLoader[] classLoaders = getClassLoaders();
for (ClassLoader classLoader : classLoaders) {
InputStream inputStream = classLoader.getResourceAsStream(resource);
if (null != inputStream) {
return inputStream;
}
}
throw new IOException("Could not find resource " + resource);
}
private static ClassLoader[] getClassLoaders() {
return new ClassLoader[]{
ClassLoader.getSystemClassLoader(),
Thread.currentThread().getContextClassLoader()};
}
/**
* 获取类示例
* @param className 类名称
* @return 实例类
*/
public static Class<?> classForName(String className) throws ClassNotFoundException {
return Class.forName(className);
}
}
SqlCommandType
package com.lino.mybatis.mapping;
/**
* @description: SQL指令
*/
public enum SqlCommandType {
/**
* 未知
*/
UNKNOWN,
/**
* 插入
*/
INSERT,
/**
* 更新
*/
UPDATE,
/**
* 删除
*/
DELETE,
/**
* 查找
*/
SELECT
}
MappedStatement.java
package com.lino.mybatis.mapping;
import com.lino.mybatis.session.Configuration;
import java.util.Map;
/**
* @description: 映射器语句类
*/
public class MappedStatement {
private Configuration configuration;
private String id;
private SqlCommandType sqlCommandType;
private String parameterType;
private String resultType;
private String sql;
private Map<Integer, String> parameter;
public MappedStatement() {
}
public static class Builder {
private MappedStatement mappedStatement = new MappedStatement();
public Builder(Configuration configuration, String id, SqlCommandType sqlCommandType, String parameterType, String resultType, String sql, Map<Integer, String> parameter) {
mappedStatement.configuration = configuration;
mappedStatement.id = id;
mappedStatement.sqlCommandType = sqlCommandType;
mappedStatement.parameterType = parameterType;
mappedStatement.resultType = resultType;
mappedStatement.sql = sql;
mappedStatement.parameter = parameter;
}
public MappedStatement build() {
assert mappedStatement.configuration != null;
assert mappedStatement.id != null;
return mappedStatement;
}
}
public Configuration getConfiguration() {
return configuration;
}
public String getId() {
return id;
}
public SqlCommandType getSqlCommandType() {
return sqlCommandType;
}
public String getParameterType() {
return parameterType;
}
public String getResultType() {
return resultType;
}
public String getSql() {
return sql;
}
public Map<Integer, String> getParameter() {
return parameter;
}
}
Configuration.java
package com.lino.mybatis.session;
import com.lino.mybatis.binding.MapperRegistry;
import com.lino.mybatis.mapping.MappedStatement;
import java.util.HashMap;
import java.util.Map;
/**
* @description: 配置项
*/
public class Configuration {
/**
* 映射注册机
*/
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
/**
* 映射的语句,存在Map里
*/
protected final Map<String, MappedStatement> mappedStatements = new HashMap<>(16);
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
public boolean hasMapper(Class<?> type) {
return mapperRegistry.hasMapper(type);
}
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
public MappedStatement getMappedStatement(String id) {
return mappedStatements.get(id);
}
}
MappedStatement
:新添加的 SQL 信息记录对象,包括记录:SQL类型、SQL语句、入参类型、出参类型等。MapperProxy.java
package com.lino.mybatis.binding;
import com.lino.mybatis.session.SqlSession;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
/**
* @description: 映射器代理类
*/
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
final MapperMethod mapperMethod = cacheMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
/**
* 去缓存中找MapperMethod
*/
private MapperMethod cacheMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
MapperProxyFactory.java
package com.lino.mybatis.binding;
import com.lino.mybatis.session.SqlSession;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @description: 映射器代理工厂
*/
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
}
MapperRegistry.java
package com.lino.mybatis.binding;
import cn.hutool.core.lang.ClassScanner;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.SqlSession;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @description: 映射器注册机
*/
public class MapperRegistry {
protected Configuration configuration;
public MapperRegistry(Configuration configuration) {
this.configuration = configuration;
}
/**
* 将已添加的映射器代理加入到HashMap
*/
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>(16);
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new RuntimeException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new RuntimeException("Error getting mapper instance. Cause: " + e, e);
}
}
public <T> void addMapper(Class<T> type) {
/* Mapper 必须是接口才会注册 */
if (type.isInterface()) {
if (hasMapper(type)) {
// 如果重复添加,报错
throw new RuntimeException("Type " + type + " is already known to the MapperRegistry.");
}
// 注册映射器代理工厂
knownMappers.put(type, new MapperProxyFactory<T>(type));
}
}
public <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}
public void addMappers(String packageName) {
Set<Class<?>> mapperSet = ClassScanner.scanPackage(packageName);
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
}
MapperMethod.java
package com.lino.mybatis.binding;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.SqlSession;
import java.lang.reflect.Method;
/**
* @description: 映射器方法
*/
public class MapperMethod {
private final SqlCommand command;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration configuration) {
this.command = new SqlCommand(configuration, mapperInterface, method);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result = null;
switch (command.getType()) {
case INSERT:
break;
case DELETE:
break;
case UPDATE:
break;
case SELECT:
result = sqlSession.selectOne(command.getName(), args);
break;
default:
throw new RuntimeException("Unknown execution method for: " + command.getName());
}
return result;
}
/**
* SQL 指令
*/
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = configuration.getMappedStatement(statementName);
this.name = ms.getId();
this.type = ms.getSqlCommandType();
}
public String getName() {
return name;
}
public SqlCommandType getType() {
return type;
}
}
}
BaseBuilder.java
package com.lino.mybatis.builder;
import com.lino.mybatis.session.Configuration;
/**
* @description: 构建器的基类,建造者模式
*/
public class BaseBuilder {
protected final Configuration configuration;
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
}
public Configuration getConfiguration() {
return configuration;
}
}
XMLConfigBuilder.java
package com.lino.mybatis.builder.xml;
import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;
import java.io.Reader;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @description: XML配置构建器,建造者模式,集成BaseBuilder
*/
public class XMLConfigBuilder extends BaseBuilder {
private Element root;
private static final Pattern pattern = Pattern.compile("(#\\{(.*?)})");
public XMLConfigBuilder(Reader reader) {
// 1.调用父类初始化Configuration
super(new Configuration());
// 2.dom4j 处理xml
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(new InputSource(reader));
root = document.getRootElement();
} catch (DocumentException e) {
e.printStackTrace();
}
}
/**
* 解析配置:类型别名、插件、对象工厂、对象包装工厂、设置、环境、类型转换、映射器
*
* @return Configuration
*/
public Configuration parse() {
try {
mapperElement(root.element("mappers"));
} catch (Exception e) {
throw new RuntimeException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
return configuration;
}
private void mapperElement(Element mappers) throws Exception {
List<Element> mapperList = mappers.elements("mapper");
for (Element e : mapperList) {
String resource = e.attributeValue("resource");
Reader reader = Resources.getResourceAsReader(resource);
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new InputSource(reader));
Element root = document.getRootElement();
// 命名空间
String namespace = root.attributeValue("namespace");
// SELECT
List<Element> selectNodes = root.elements("select");
for (Element node : selectNodes) {
String id = node.attributeValue("id");
String parameterType = node.attributeValue("parameterType");
String resultType = node.attributeValue("resultType");
String sql = node.getText();
// ? 匹配
Map<Integer, String> parameter = new HashMap<>(16);
Matcher matcher = pattern.matcher(sql);
for (int i = 1; matcher.find(); i++) {
String g1 = matcher.group(1);
String g2 = matcher.group(2);
parameter.put(i, g2);
sql = sql.replace(g1, "?");
}
String msId = namespace + "." + id;
String nodeName = node.getName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
MappedStatement mappedStatement = new MappedStatement.Builder(configuration, msId, sqlCommandType, parameterType, resultType, sql, parameter).build();
// 添加解析SQL
configuration.addMappedStatement(mappedStatement);
}
// 注册Mapper映射器
configuration.addMapper(Resources.classForName(namespace));
}
}
}
XMLConfigBuilder
核心操作在于初始化 Configuration
,因为 Configuration
的使用离解析 XML 和存放是最近的操作。parse()
解析操作,并把解析后的信息,通过 Configuration
配置类进行存放,包括:添加解析 SQL、注册 Mapper 映射器。SqlSession.java
package com.lino.mybatis.session;
/**
* @description: SqlSession 用来执行SQL,获取映射器,管理事务
*/
public interface SqlSession {
/**
* 根据指定的sqlID获取一条记录的封装对象
*
* @param statement sqlID
* @param 封装之后的对象类型
* @return 封装之后的对象
*/
<T> T selectOne(String statement);
/**
* 根据指定的sqlID获取一条记录的封装对象,只不过这个方法容许我们给sql传递一些参数
*
* @param statement sqlID
* @param parameter 传递参数
* @param 封装之后的对象类型
* @return 封装之后的对象
*/
<T> T selectOne(String statement, Object parameter);
/**
* 得到映射器,使用泛型,使得类型安全
*
* @param type 对象类型
* @param 封装之后的对象类型
* @return 封装之后的对象
*/
<T> T getMapper(Class<T> type);
/**
* 获取配置类
*
* @return Configuration
*/
Configuration getConfiguration();
}
DefaultSqlSession.java
package com.lino.mybatis.session.defaults;
import com.lino.mybatis.binding.MapperRegistry;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.SqlSession;
/**
* @description: 默认sqlSession实现类
*/
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <T> T selectOne(String statement) {
return (T) ("你被代理了!" + statement);
}
@Override
public <T> T selectOne(String statement, Object parameter) {
MappedStatement mappedStatement = configuration.getMappedStatement(statement);
return (T) ("你被代理了!" + "方法:" + statement + "入参:" + parameter + "\n待执行SQL:" + mappedStatement.getSql());
}
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
@Override
public Configuration getConfiguration() {
return configuration;
}
}
DefaultSqlSession
:把 MapperRegistry
替换为 Configuration
,这样能传递更丰富的信息内容,而不只是注册器操作。DefaultSqlSession#selectOne
、DefaultSqlSession#getMapper
两个方法中都使用 Configuration
来获取对应的信息。SqlSessionFactory.java
package com.lino.mybatis.session;
/**
* @description: 工厂模式接口,构建SqlSession的工厂
*/
public interface SqlSessionFactory {
/**
* 打开一个session
*
* @return SqlSession
*/
SqlSession openSession();
}
DefaultSqlSessionFactory.java
package com.lino.mybatis.session.defaults;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.SqlSession;
import com.lino.mybatis.session.SqlSessionFactory;
/**
* @description: 默认的SqlSessionFactory实现类
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(configuration);
}
}
SqlSessionFactoryBuilder.java
package com.lino.mybatis.session;
import com.lino.mybatis.builder.xml.XMLConfigBuilder;
import com.lino.mybatis.session.defaults.DefaultSqlSessionFactory;
import java.io.Reader;
/**
* @description: 构建SqlSession的工厂, 建造者模式
*/
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader) {
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(reader);
return build(xmlConfigBuilder.parse());
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
SqlSessionFactoryBuilder
是作为整个 Mybatis 的入口类,通过指定解析 XML 的 IO,引导整个流程的启动。XMLConfigBuilder
、Configuration
两个处理类,分别用于解析 XML 和串联整个流程的对象保存操作。IUserDao.java
package com.lino.mybatis.test.dao;
/**
* @Description: 用户持久层
*/
public interface IUserDao {
/**
* 根据ID查询用户信息
*
* @param uId ID
* @return String 名称
*/
String queryUserInfoById(String uId);
}
User.java
package com.lino.mybatis.test.po;
import java.time.LocalDateTime;
/**
* @description: 用户实例类
*/
public class User {
private Long id;
/**
* 用户ID
*/
private String userId;
/**
* 头像
*/
private String userHead;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserHead() {
return userHead;
}
public void setUserHead(String userHead) {
this.userHead = userHead;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
}
mybatis-config-datasource.xml
DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<mappers>
<mapper resource="mapper/User_Mapper.xml">mapper>
mappers>
configuration>
User_Mapper.xml
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lino.mybatis.test.dao.IUserDao">
<select id="queryUserInfoById" parameterType="java.lang.Long" resultType="com.lino.mybatis.test.po.User">
SELECT id, userId, userHead, createTime
FORM user
where id = #{id}
select>
mapper>
ApiTest.java
package com.lino.mybatis.test;
import com.alibaba.fastjson.JSON;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.session.SqlSession;
import com.lino.mybatis.session.SqlSessionFactory;
import com.lino.mybatis.session.SqlSessionFactoryBuilder;
import com.lino.mybatis.test.dao.IUserDao;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.Reader;
/**
* @description: 单元测试
*/
public class ApiTest {
private Logger logger = LoggerFactory.getLogger(ApiTest.class);
/**
* 测试映射器注册机
*/
@Test
public void test_SqlSessionFactory() throws IOException {
// 1.从SqlSessionFactory中获取SqlSession
Reader reader = Resources.getResourceAsReader("mybatis-config-datasource.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 2.获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 3.测试验证
String result = userDao.queryUserInfoById("1");
logger.info("测试结果:{}", JSON.toJSONString(result));
}
}
SqlSessionFactoryBuilder
进行构建解析,并获取 SqlSessionFactory
工厂,之后开启 SqlSession
完成后续操作。测试结果
15:30:35.982 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果:你被代理了!方法:com.lino.mybatis.test.dao.IUserDao.queryUserInfoById入参:[Ljava.lang.Object;@2aafb23c
待执行SQL:
SELECT id, userId, userHead, createTime
FORM user
where id = ?