一:概述
最近在学习SSM框架,有时间来整合一下,想深入了解一下框架底层,先手动实现一下mybatis。
完整代码链接:点击打开链接
首先先总结一下SSM框架的大体内容:
SSM框架在项目开发中经常使用到,相比于SSH框架,它在仅几年的开发中运用的更加广泛。
- Spring作为一个轻量级的框架,有很多的拓展功能,最主要的我们一般项目使用的就是IOC和AOP。
- SpringMVC是Spring实现的一个Web层,相当于Struts的框架,但是比Struts更加灵活和强大!
- Mybatis是 一个持久层的框架,在使用上相比Hibernate更加灵活,可以控制sql的编写,使用 XML或注解进行相关的配置
这次就模拟mybatis实现数据层的持久化。
二:编写思路
1.先来看一下项目目录结构:
1)这里我使用oracle数据库进行测试,引入了oracle的驱动包。
2)mybatis是使用xml或注解来配置和映射原生信息。避免了繁琐的强耦合的JDBC代码。这里模拟xml配置过程,首先就要对xml配置文件进行解析。使用dom4j来解析xml文件,引入相关包。
2.mybatis-config.xml文件:
首先要对这样一个xml文件进行解析,这个文件对应的是mybatis的配置xml文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<dataSource>
<property name="driver" value="oracle.jdbc.driver.OracleDriver"></property>
<property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:orcl"></property>
<property name="user" value="vera"></property>
<property name="password" value="a"></property>
</dataSource>
<mappers>
<mapper resource="com/yee/mybatis/bean/DeptMapper.xml" />
</mappers>
</configuration>
解析是要获取两个对象:
1) DataSource对象
2)mappers下的映射文件路径(com/yee/mybatis/bean/DeptMapper.xml)
编写了一个MybatisConfig来解析
3.MybatisConfig解析
可以看到,生成对应的DataSource对象,这个对象是mybatis的内置对象,我模拟提供了如下属性。(这里只是简单的模拟,真正mybatis封装的内容非常多)
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
例如:
<dataSource type="POOLED">
的type属性、其有三种取值:
- POOLED:使用Mybatis自带的数据库连接池来管理数据库连接
- UNPOOLED:不使用任何数据库连接池来管理数据库连接
- JNDI:jndi形式使用数据库连接、主要用于项目正常使用的时候
4.Data Source 对象
根据mybatis-config中DataSource的属性我提供了包含这几个属性的实体类 DataSource:
p.s:这个是自己手动模拟的!!不是官方提供的!!不要误解
p.s:这个是自己手动模拟的!!不是官方提供的!!不要误解
package com.yee.mybatis;
public class DataSource {
private String driver;
private String url;
private String user;
private String password;
@Override
public String toString() {
return "DataSource [driver=" + driver + ", url=" + url + ", user=" + user + ", password=" + password + "]";
}
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
为了模拟实现 我封装了一个MapperInfo对象:
这个对象的属性是根据mybatis的实体映射属性来确定的,简单模拟,我提供了parameterType,resultMap几个常用属性。
这里的isUpdate是为了方便实现自己添加的一个属性,因为修改和查看的操作略有区别。
p.s:Dept实体类的代码就不粘了 就是属性加get(),set(),toString(),构造方法,基本操作哈;
1)MapperInfo对象
package com.yee.mybatis;
public class MapperInfo {
private String parameterType;
private String resultType;
private String sql;
private boolean isUpdate=false;
@Override
public String toString() {
return "MapperInfo [parameterType=" + parameterType + ", resultType=" + resultType + ", sql=" + sql
+ ", isUpdate=" + isUpdate + "]";
}
public String getParameterType() {
return parameterType;
}
public void setParameterType(String parameterType) {
this.parameterType = parameterType;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public boolean isUpdate() {
return isUpdate;
}
public void setUpdate(boolean isUpdate) {
this.isUpdate = isUpdate;
}
}
2)DeptMapper.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<mapper>
<insert id="addDept" parameterType="com.yee.mybatis.bean.Dept">
insert into dept values(#{deptno},#{dname},#{loc})
</insert>
<select id="findAll" resultType="com.yee.mybatis.bean.Dept">
select deptno,dname,loc from dept
</select>
</mapper>
MapperInfo对象就是保存这个映射文件的基本信息的,下面开始解析DeptMapper.xml文件并进行处理。
三:核心处理
1.SqlSessionFactory对象
读取并解析映射文件 DeptMapper.xml 获取MapperInfo对象
package com.yee.mybatis;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;
public class SqlSessionFactory {
private MyBatisConfig config;
private Map<String,MapperInfo> mapperInfos=new HashMap<String,MapperInfo>();
public SqlSessionFactory(MyBatisConfig config){
this.config=config;
try {
parseXml();
} catch (DocumentException e) {
e.printStackTrace();
}
}
@SuppressWarnings("unchecked")
private void parseXml() throws DocumentException{
List<String> mappers=config.getMappers();
if(mappers!=null && mappers.size()>0){
SAXReader saxReader=new SAXReader();
for(String mapper:mappers){
Document doc=saxReader.read(this.getClass().getClassLoader().getResourceAsStream(mapper));
XPath xpath=doc.createXPath("//mapper/*");
List<Element> ops=xpath.selectNodes(doc);
MapperInfo info=null;
String opname=null;
for(Element el:ops){
info=new MapperInfo();
opname=el.getName(); //获取操作类型 即元素名 insert
if("select".equals(opname)){
info.setUpdate(false); //不为更新操作
}
info.setParameterType(el.attributeValue("parameterType"));
info.setResultType(el.attributeValue("resultType"));
info.setSql(el.getTextTrim());
mapperInfos.put(el.attributeValue("id"),info);
}
}
}
}
public MyBatisConfig getConfig() {
return config;
}
public Map<String, MapperInfo> getMapperInfos() {
return mapperInfos;
}
}
2.SqlSession对象
sqlSession对象封装了传统的DBHelper实现数据层的功能,为上层提供更为简洁易懂的接口。
1.先贴出查找的代码
package com.yee.mybatis;
import java.util.List;
public class SqlSession {
private SqlSessionFactory factory;
private DBHelper db;
public SqlSession(SqlSessionFactory factory){
this.factory=factory;
db=new DBHelper(factory.getConfig().getDataSource());
}
/**
* 按要求返回查找的数据
* @param sqlId
* @param params
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public <T>List<T> selectList(String sqlId,Object ... params){
//获取sql
//获取参数列表
MapperInfo mapperInfo=factory.getMapperInfos().get(sqlId); //键值对存储可以快速获取 addDept findAll 和 对应的属性 parameterType resultMap
if(mapperInfo!=null){
try {
String sql=mapperInfo.getSql();
if(mapperInfo.isUpdate()){//如果是更新
return null;
}else{ //为查询
String className=mapperInfo.getResultType();
Class c=Class.forName(className);
//调用数据层返回结果
return db.find(sql, c, params);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return null;
}
四:总结体会
1.关于框架
sqlSession对象封装了传统的DBHelper实现数据层的功能,为上层提供更为简洁易懂的接口。这其实就是框架的意义所在,将底层复杂的东西封装好,对用户“隐藏”,提供处理过后的接口。也是框架搭建慢,使用方便的原因,底层各种映射,这样的处理比起“原生处理”要慢。但是框架提供了一致易于理解的接口,还是广泛应用于团队开发中,框架总的来说就是一种规范一种制度。
2.关于“原生态”编程
使用框架我们往往可以做到快速开发,迅速搭建,很多东西都交给框架去做,框架底层多用映射机制,速度比“原生”满。很多时候,如果要追求效率,就得加上优化处理。学习框架,不仅要会用,更要花时间去了解更为底层的原理和实现。
至此,我们已经完成了仿Mybatis的查找功能,增加删除和测试部分放到另一篇博客中。后续有时间还会继续对spring IOC spring AOP进行分析和模拟实现。