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