手撸Mybatis(二)—— 配置项的获取

本专栏的源码:https://gitee.com/dhi-chen-xiaoyang/yang-mybatis。

配置项解析

在mybatis中,一般我们会定义一个mapper-config.xml文件,来配置数据库连接的相关信息,以及我们的mapperxml文件存放目录。在本章,我们会读取这些文件,将其配置信息进行解析。
因为涉及到xml的解析,因此,我们先添加dom4j的依赖,以方便后续解析xml

<dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>

mybatis-config.xml的内容如下,对于每一个环境,都有对应的数据源信息,此外,mappers标签存储的是mapper.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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.102.128:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

首先,我们添加MybatisDataSource,用来记录数据源信息

package com.yang.mybatis.config;

import java.io.Serializable;

public class MybatisDataSource implements Serializable {
    private String driver;
    private String url;
    private String username;
    private String password;

    public String getDriver() {
        return driver;
    }

    public String getUrl() {
        return url;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

然后添加一个MybatisEnvironment,用来存储环境信息

package com.yang.mybatis.config;

import java.io.Serializable;

public class MybatisEnvironment implements Serializable {
    private String id;

    private MybatisDataSource mybatisDataSource;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public MybatisDataSource getMybatisDataSource() {
        return mybatisDataSource;
    }

    public void setMybatisDataSource(MybatisDataSource mybatisDataSource) {
        this.mybatisDataSource = mybatisDataSource;
    }
}

然后是MybatisConfiguration,我们读取mybatis-config.xml配置文件后,配置信息就存储在这个类中。

package com.yang.mybatis.config;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MybatisConfiguration implements Serializable {
    private Map<String, MybatisEnvironment> id2EnvironmentMap = new HashMap<>();

    private MybatisEnvironment defaultMybatisEnvironment;

    private List<String> mapperPaths = new ArrayList<>();

    public void addEnvironment(String id, MybatisEnvironment mybatisEnvironment) {
        this.id2EnvironmentMap.put(id, mybatisEnvironment);
    }

    public MybatisEnvironment getEnvironment(String id) {
        return id2EnvironmentMap.get(id);
    }

    public MybatisEnvironment getDefaultMybatisEnvironment() {
        return defaultMybatisEnvironment;
    }

    public void setDefaultMybatisEnvironment(MybatisEnvironment defaultMybatisEnvironment) {
        this.defaultMybatisEnvironment = defaultMybatisEnvironment;
    }

    public void addMapperPath(String mapperPath) {
        this.mapperPaths.add(mapperPath);
    }

    public List<String> getMapperPaths() {
        return this.mapperPaths;
    }

    public List<MybatisEnvironment> getEnvironments() {
        return new ArrayList<>(id2EnvironmentMap.values());
    }

}

对于mybatis-config.xml的解析,我们定义一个IMybatisConfigParser接口

package com.yang.mybatis.config.parser;

import com.yang.mybatis.config.MybatisConfiguration;

public interface IMybatisConfigurationParser {
    MybatisConfiguration parser(String path) ;
}

然后定义其实现类:

package com.yang.mybatis.config.parser;

import com.yang.mybatis.config.MybatisConfiguration;
import com.yang.mybatis.config.MybatisDataSource;
import com.yang.mybatis.config.MybatisEnvironment;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class XmlMybatisConfigurationParser implements IMybatisConfigurationParser {

    @Override
    public MybatisConfiguration parser(String path) {
        MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();
        try {
            InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(path);
            SAXReader saxReader = new SAXReader();
            // 1. 读取文档
            Document document = saxReader.read(inputStream);
            Element root = document.getRootElement();

            parseEnvironments(mybatisConfiguration, root.element("environments"));
            parseMappers(mybatisConfiguration, root.element("mappers"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return mybatisConfiguration;
    }

    private void parseMappers(MybatisConfiguration mybatisConfiguration, Element mappers) {
        List<Element> mapperList = mappers.elements("mapper");
        for (Element mapper : mapperList) {
            String resource = mapper.attributeValue("resource");
            mybatisConfiguration.addMapperPath(resource);
        }
    }

    private void parseEnvironments(MybatisConfiguration mybatisConfiguration, Element environments) {
        String defaultId = environments.attributeValue("default");
        for (Element element : environments.elements()) {
            String id = element.attributeValue("id");

            Element dataSource = element.element("dataSource");
            List<Element> properties = dataSource.elements("property");
            Map<String, String> propertyMap = new HashMap<>();
            for (Element property : properties) {
                propertyMap.put(property.attributeValue("name"), property.attributeValue("value"));
            }
            String driver = propertyMap.get("driver");
            String url = propertyMap.get("url");
            String username = propertyMap.get("username");
            String password = propertyMap.get("password");
            MybatisDataSource mybatisDataSource = new MybatisDataSource();
            mybatisDataSource.setDriver(driver);
            mybatisDataSource.setUrl(url);
            mybatisDataSource.setUsername(username);
            mybatisDataSource.setPassword(password);

            MybatisEnvironment mybatisEnvironment = new MybatisEnvironment();
            mybatisEnvironment.setId(id);
            mybatisEnvironment.setMybatisDataSource(mybatisDataSource);
            mybatisConfiguration.addEnvironment(id, mybatisEnvironment);
            if (id.equals(defaultId)) {
                mybatisConfiguration.setDefaultMybatisEnvironment(mybatisEnvironment);
            }
        }
    }
}

然后添加测试代码进行测试:

 IMybatisConfigurationParser mybatisConfigurationParser = new XmlMybatisConfigurationParser();
        MybatisConfiguration mybatisConfiguration = mybatisConfigurationParser.parser("mybatis-config.xml");
        System.out.println("mapperPaths==========");
        for (String mapperPath : mybatisConfiguration.getMapperPaths()) {
            System.out.println(mapperPath);
        }
        System.out.println("environments========");
        for (MybatisEnvironment environment : mybatisConfiguration.getEnvironments()) {
            MybatisDataSource mybatisDataSource = environment.getMybatisDataSource();
            System.out.println("id:" + environment.getId()
            + ",driver:" + mybatisDataSource.getDriver()
            + ",url:" + mybatisDataSource.getUrl()
            + ",username:" + mybatisDataSource.getUsername()
            + ",password:" + mybatisDataSource.getPassword());
        }
        System.out.println("defaultEnvironment=======");
        MybatisEnvironment defaultMybatisEnvironment = mybatisConfiguration.getDefaultMybatisEnvironment();
        MybatisDataSource mybatisDataSource = defaultMybatisEnvironment.getMybatisDataSource();
        System.out.println("id:" + defaultMybatisEnvironment.getId()
                + ",driver:" + mybatisDataSource.getDriver()
                + ",url:" + mybatisDataSource.getUrl()
                + ",username:" + mybatisDataSource.getUsername()
                + ",password:" + mybatisDataSource.getPassword());

结果如下:
image.png

mapperXml的解析

首先添加一个MybatisSqlStatement

package com.yang.mybatis.config;

import java.io.Serializable;

public class MybatisSqlStatement implements Serializable {
    private String namespace;

    private String id;

    private String sql;

    public String getNamespace() {
        return namespace;
    }

    public void setNamespace(String namespace) {
        this.namespace = namespace;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }
}

添加解析mapper文件的接口

package com.yang.mybatis.config.parser;

import com.yang.mybatis.config.MybatisSqlStatement;

import java.util.List;

public interface IMybatisMapperParser {
    List<MybatisSqlStatement> parseMapper(String path);
}

具体实现:

package com.yang.mybatis.config.parser;

import com.yang.mybatis.config.MybatisSqlStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class XmlMybatisMapperParser implements IMybatisMapperParser {
    private final static Set<String> tagSet = new HashSet<>();

    static {
        tagSet.add("select");
        tagSet.add("insert");
        tagSet.add("update");
        tagSet.add("delete");
        tagSet.add("SELECT");
        tagSet.add("INSERT");
        tagSet.add("UPDATE");
        tagSet.add("DELETE");
    }
    @Override
    public List<MybatisSqlStatement> parseMapper(String path) {
        List<MybatisSqlStatement> mybatisSqlStatements = new ArrayList<>();
        try {
            InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(path);
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(inputStream);
            Element root = document.getRootElement();

            for (String tag : tagSet) {
                List<Element> elements = root.elements(tag);
                parseStatement(mybatisSqlStatements, elements, root);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return mybatisSqlStatements;
    }

    private void parseStatement(List<MybatisSqlStatement> mybatisSqlStatements, List<Element> elements, Element root) {
        if (elements == null || elements.isEmpty()) {
            return;
        }
        String namespace = root.attributeValue("namespace");
        for (Element element : elements) {
            String id = element.attributeValue("id");
            String sql = element.getText().trim();

            MybatisSqlStatement mybatisSqlStatement = new MybatisSqlStatement();
            mybatisSqlStatement.setNamespace(namespace);
            mybatisSqlStatement.setId(id);
            mybatisSqlStatement.setSql(sql);

            mybatisSqlStatements.add(mybatisSqlStatement);
        }
    }
}

然后,我们创建一个UserMapper.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.yang.mybatis.test.IUserMapper">
    <select id="queryUserName">
        select user_name from user where id = #{id}
    </select>
    <select id="queryUserAge">
        select age from user where id = #{id}
    </select>
</mapper>

添加测试代码,进行测试:

    public static void main(String[] args) {
        IMybatisMapperParser mybatisMapperParser = new XmlMybatisMapperParser();
        List<MybatisSqlStatement> mybatisSqlStatements = mybatisMapperParser.parseMapper("mapper/UserMapper.xml");
        for (MybatisSqlStatement mybatisSqlStatement : mybatisSqlStatements) {
            System.out.println("=================");
            System.out.println("namespace:" + mybatisSqlStatement.getNamespace());
            System.out.println("id:" + mybatisSqlStatement.getId());
            System.out.println("sql:" + mybatisSqlStatement.getSql());
        }
    }

结果如下:
image.png

MapperProxyFactory注册Mapper

这里再修改一些MybatisConfiguration,因为mapperXML里的内容,其实我认为也属于配置项,因此收敛到MybatisConfiguration,方便后续统一管理

package com.yang.mybatis.config;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MybatisConfiguration implements Serializable {
    private Map<String, MybatisEnvironment> id2EnvironmentMap = new HashMap<>();

    private MybatisEnvironment defaultMybatisEnvironment;

    private List<String> mapperPaths = new ArrayList<>();

    private Map<String, List<MybatisSqlStatement>> mapper2SqlStatementsMap = new HashMap<>();

    public void putMybatisSqlStatementList(String mapperName, List<MybatisSqlStatement> mybatisSqlStatementList) {
        if (mapper2SqlStatementsMap.containsKey(mapperName)) {
            mapper2SqlStatementsMap.get(mapperName).addAll(mybatisSqlStatementList);
        } else {
            mapper2SqlStatementsMap.put(mapperName, mybatisSqlStatementList);
        }
    }

    public Map<String, List<MybatisSqlStatement>> getMapper2SqlStatementsMap() {
        return this.mapper2SqlStatementsMap;
    }

    public void addEnvironment(String id, MybatisEnvironment mybatisEnvironment) {
        this.id2EnvironmentMap.put(id, mybatisEnvironment);
    }

    public MybatisEnvironment getEnvironment(String id) {
        return id2EnvironmentMap.get(id);
    }

    public MybatisEnvironment getDefaultMybatisEnvironment() {
        return defaultMybatisEnvironment;
    }

    public void setDefaultMybatisEnvironment(MybatisEnvironment defaultMybatisEnvironment) {
        this.defaultMybatisEnvironment = defaultMybatisEnvironment;
    }

    public void addMapperPath(String mapperPath) {
        this.mapperPaths.add(mapperPath);
    }

    public List<String> getMapperPaths() {
        return this.mapperPaths;
    }

    public List<MybatisEnvironment> getEnvironments() {
        return new ArrayList<>(id2EnvironmentMap.values());
    }

}

然后修改MapperProxy,接收MybatisSqlStatement数组,在执行的时候,根据执行方法,找到对应的MybatisSqlStatement,获取mapper Xml对应的sql语句,并打印出来

package com.yang.mybatis.mapper;

import com.yang.mybatis.config.MybatisSqlStatement;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MapperProxy<T> implements InvocationHandler {
    private Map<String, MybatisSqlStatement> sqlSessionMap = new HashMap<>();

    private Class<T> mapperInterface;

    public MapperProxy(Class<T> mapperInterface, List<MybatisSqlStatement> mybatisSqlStatementList) {
        this.mapperInterface = mapperInterface;
        for (MybatisSqlStatement mybatisSqlStatement : mybatisSqlStatementList) {
            String mapperName = mybatisSqlStatement.getNamespace();
            String id = mybatisSqlStatement.getId();
            String key = mapperName + "." + id;
            sqlSessionMap.put(key, mybatisSqlStatement);
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }
        String key = this.mapperInterface.getName() + "." + method.getName();
        return "你的被代理了!" + sqlSessionMap.get(key).getSql();
    }
}

接着修改MybatisProxyFactory,通过配置信息,加载对应的mapper.xml文件并进行解析

package com.yang.mybatis.mapper;

import com.yang.mybatis.config.MybatisConfiguration;
import com.yang.mybatis.config.MybatisSqlStatement;

import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MapperProxyFactory {
    private Map<Class, MapperProxy> mapperProxyMap = new HashMap<>();


    public MapperProxyFactory(MybatisConfiguration mybatisConfiguration) {
        Map<String, List<MybatisSqlStatement>> mapperName2SqlStatementsMap = mybatisConfiguration.getMapper2SqlStatementsMap();
        mapperName2SqlStatementsMap.forEach((mapperName, sqlStatementList) -> {
            try {
                Class<?> type = Class.forName(mapperName);
                mapperProxyMap.put(type, new MapperProxy(type, sqlStatementList));
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        });
    }

    public Object newInstance(Class mapperType) {
        MapperProxy mapperProxy = mapperProxyMap.get(mapperType);
        return Proxy.newProxyInstance(mapperType.getClassLoader(),
                new Class[]{mapperType},
                mapperProxy);
    }

}

添加测试方法,进行测试:

public static void main(String[] args) {
        IMybatisConfigurationParser iMybatisConfigurationParser = new XmlMybatisConfigurationParser();
        MybatisConfiguration mybatisConfiguration = iMybatisConfigurationParser.parser("mybatis-config.xml");

        IMybatisMapperParser iMybatisMapperParser = new XmlMybatisMapperParser();
        List<String> mapperPaths = mybatisConfiguration.getMapperPaths();
        for (String mapperPath : mapperPaths) {
            List<MybatisSqlStatement> mybatisSqlStatements = iMybatisMapperParser.parseMapper(mapperPath);
            Map<String, List<MybatisSqlStatement>> mapperNameGroupMap = mybatisSqlStatements.stream()
                    .collect(Collectors.groupingBy(MybatisSqlStatement::getNamespace));

            for (Map.Entry<String, List<MybatisSqlStatement>> entry : mapperNameGroupMap.entrySet()) {
                String mapperName = entry.getKey();
                List<MybatisSqlStatement> sqlSessionList = entry.getValue();
                mybatisConfiguration.putMybatisSqlStatementList(mapperName, sqlSessionList);
            }
        }

        MapperProxyFactory mapperProxyFactory = new MapperProxyFactory(mybatisConfiguration);
        IUserMapper userMapper = (IUserMapper) mapperProxyFactory.newInstance(IUserMapper.class);
        System.out.println(userMapper.queryUserName(1));
    }

测试结果如下:
image.png

参考文章

https://zhuanlan.zhihu.com/p/338300626

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值