本专栏的源码: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 configurationPUBLIC "-//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 {@Overridepublic 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());
结果如下:
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");}@Overridepublic 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 mapperPUBLIC "-//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());}}
结果如下:
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);}}@Overridepublic 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));}
测试结果如下:
参考文章
https://zhuanlan.zhihu.com/p/338300626