文章目录
手写MyBatis框架
需求定义
使用手写框架,根据用户ID查询用户信息
项目搭建
框架代码结构图
设计配置文件
全局配置文件
<configuration>
<!-- MyBatis 数据源环境配置 -->
<environments default="dev">
<environment id="dev">
<!-- 配置数据源信息 -->
<dataSource type="DBCP">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://192.168.152.100:3306/ssm" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<!-- 映射文件加载 -->
<mappers>
<!-- resource指定映射文件的类路径 -->
<mapper resource="mapper/UserMapper.xml" />
</mappers>
</configuration>
mapper.xml文件
<mapper namespace="test">
<!-- select标签,封装了SQL语句信息、入参类型、结果映射类型 -->
<select id="findUserById"
parameterType="com.osmond.mybatis.po.User"
resultType="com.osmond.mybatis.po.User"
statementType="prepared">
SELECT * FROM user WHERE id = #{id} AND username like '%${username}'
<if test="username != null and username !='' ">
AND username like '%${username}'
<if test="username != null and username !=''">
AND 1=1
</if>
</if>
</select>
</mapper>
Code:解析全局配置文件
定义Configuration类与主解析类
当前我们只需要从全局配置文件中解析出标签下的数据库连接配置,因此先在Configuration.java中定义DataSource对象。
public class Configuration {
private DataSource dataSource;
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
将全局配置文件的解析放在一个单独的类中进行处理:XMLConfigParser.java,该类最后是将整个全局配置文件的内容解析成Configuration,因此我们在类中定义Configuration。
我们假定在全局配置文件解析前,会通过一系列手段转换成Element对象(使用Dom4j可以使xml文件转换成Element).所以parse的方法入参为Element。
public class XMLConfigParser {
private Configuration configuration;
public XMLConfigParser() {
configuration = new Configuration();
}
/**
*
* @param rootElement
* <configuration>
* @return
*/
public Configuration parse(Element rootElement) {
parseEnvironments(rootElement.element("environments"));
parseMappers(rootElement.element("mappers"));
return configuration;
}
}
解析标签
标签中需要解析:
- default属性
- N多个标签(只解析id与environments标签的default一致的那个)
public class XMLConfigParser {
...
/**
* 解析environments标签
* @param element
* <environments>
*/
private void parseEnvironments(Element element) {
String defaultEnvId = element.attributeValue("default");
if (defaultEnvId == null || "".equals(defaultEnvId)) {
return;
}
List<Element> elements = element.elements("environment");
for (Element envElement : elements) {
String envId = envElement.attributeValue("id");
// 判断defaultEnvId和envId是否一致,一致再继续解析
if (defaultEnvId.equals(envId)) {
parseEnvironment(envElement);
}
}
}
}
解析标签
标签内部只包含一个标签,标签下包含多个标签。这些标签解析后得到一个Properties对象,我们使用Properties对象来组装DataSource对象,并放入Configuration对象中。
public class XMLConfigParser {
...
/**
* 解析environment标签
* @param envElement
* <environment>
*/
private void parseEnvironment(Element envElement) {
Element dataSourceEnv = envElement.element("dataSource");
String type = dataSourceEnv.attributeValue("type");
type = type == null || type.equals("") ? "DBCP" : type;
if ("DBCP".equals(type)) {
Properties properties = parseProperty(dataSourceEnv);
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(properties.getProperty("driver"));
dataSource.setUrl(properties.getProperty("url"));
dataSource.setUsername(properties.getProperty("username"));
dataSource.setPassword(properties.getProperty("password"));
// 将解析出来的DataSource对象,封装到Configuration对象中
configuration.setDataSource(dataSource);
}
}
/**
* 解析dataSource标签中的多个property
* @param dataSourceEnv
* @return
*/
private Properties parseProperty(Element dataSourceEnv) {
Properties properties = new Properties();
List<Element> elements = dataSourceEnv.elements("property");
for (Element element : elements) {
String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.put(name, value);
}
return properties;
}
}
至此,Configuration对象中的DataSource属性解析完毕了。
解析标签
标签中包含多个标签,其resource属性指向了一个Mapper.xml的路径信息,因此我们读取到每个resource就可以通过一定方式获取到Mapper.xml的内容,进而转化成Document去解析。
在parseMapper方法中,我们通过定义Resources工具类获得了标签的resource属性指向的xml文件的IO流对象,随后使用DocumentUtils将IO流对象转换为了Document。
public class XMLConfigParser {
...
/**
* 解析mappers子标签,最终该标签会去解析每个映射文件
*
* @param element
*/
private void parseMappers(Element element) {
List<Element> elements = element.elements("mapper");
for (Element mapperElement : elements) {
parseMapper(mapperElement);
}
}
/**
* 解析多个mapper标签
* @param mapperElement
*/
private void parseMapper(Element mapperElement) {
// 获取映射文件的路径
String resource = mapperElement.attributeValue("resource");
// 获取指定路径的IO流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 获取映射文件对应的Document对象
Document document = DocumentUtils.readDocument(inputStream);
// 按照mapper标签语义去解析Document
...
}
}
资源文件转换工具类
/**
* 资源文件转换
* 单独提出一个工具类意义在于可以更多元化提供类似的需求
*/
public class Resources {
/**
* 转换成InputStream
* @param resource
* @return
*/
public static InputStream getResourceAsStream(String resource) {
return Resources.class.getClassLoader().getResourceAsStream(resource);
}
/**
* 转换成Reader
* @param resource
* @return
*/
public static Reader getResourceAsReader(String resource) {
// 仅做演示,未实现
return null;
}
}
IO流转Document工具类
/**
* Document转换工具类,基于Dom4J
*/
public class DocumentUtils {
public static Document readDocument(InputStream inputStream) {
try {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(inputStream);
return document;
} catch (DocumentException e) {
e.printStackTrace();
}
return null;
}
}
Test:全局配置文件读取
添加依赖
创建新项目,引入手写框架依赖及JUnit依赖
<dependencies>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- 手写MyBatis框架 -->
<dependency>
<groupId>com.osmond</groupId>
<artifactId>03-mybatis-freamwork-handwritten</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
编写测试类
测试类中同样使用手写框架中的Resources工具类及DocumentUtils工具类来获取全局配置文件的Document对象。调用XMLConfigParser的parse方法进行解析:
public class UserDaoTest {
@Test
public void testInitConfiguration() throws Exception {
// 指定全局配置文件路径
String resource = "SqlMapConfig.xml";
// 获取指定路径的IO流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 获取Document对象
Document document = DocumentUtils.readDocument(inputStream);
// 解析Document获取Configuration对象
XMLConfigParser configParser = new XMLConfigParser();
// <configuration>
Configuration configuration = configParser.parse(document.getRootElement());
System.out.println(configuration);
}
}
debug方式运行,断点打到最后一个println方法上,查看Configuration对象内容如下: