Mybatis框架设计方案
在手写自己的Mybatis框架之前,我们先来了解一下Mybatis,它的源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,才能够更深入的理解源码(ref:Mybatis源码解读-设计模式总结)。我们对上图进行分析总结:
mybatis的配置文件有2类
mybatisconfig.xml,配置文件的名称不是固定的,配置了全局的参数的配置,全局只能有一个配置文件。
Mapper.xml 配置多个statemement,也就是多个sql,整个mybatis框架中可以有多个Mappe.xml配置文件。
通过mybatis配置文件得到SqlSessionFactory
通过SqlSessionFactory得到SqlSession,用SqlSession就可以操作数据了。
SqlSession通过底层的Executor(执行器),执行器有2类实现:
基本实现
带有缓存功能的实现
MappedStatement是通过Mapper.xml中定义statement生成的对象。
参数输入执行并输出结果集,无需手动判断参数类型和参数下标位置,且自动将结果集映射为Java对象
HashMap,KV格式的数据类型
Java的基本数据类型
POJO,java的对象
二、梳理自己的Mybatis的设计思路根据上文Mybatis流程,我简化了下,分为以下步骤:
1.读取xml文件,建立连接
从图中可以看出,MyConfiguration负责与人交互。待读取xml后,将属性和连接数据库的操作封装在MyConfiguration对象中供后面的组件调用。本文将使用dom4j来读取xml文件,它具有性能优异和非常方便使用的特点。
2.创建SqlSession,搭建Configuration和Executor之间的桥梁
我们经常在使用框架时看到Session,Session到底是什么呢?一个Session仅拥有一个对应的数据库连接。类似于一个前段请求Request,它可以直接调用exec(SQL)来执行SQL语句。从流程图中的箭头可以看出,MySqlSession的成员变量中必须得有MyExecutor和MyConfiguration去集中做调配,箭头就像是一种关联关系。我们自己的MySqlSession将有一个getMapper方法,然后使用动态代理生成对象后,就可以做数据库的操作了。
3.创建Executor,封装JDBC操作数据库
Executor是一个执行器,负责SQL语句的生成和查询缓存(缓存还没完成)的维护,也就是jdbc的代码将在这里完成,不过本文只实现了单表,有兴趣的同学可以尝试完成多表。
4.创建MapperProxy,使用动态代理生成Mapper对象
我们只是希望对指定的接口生成一个对象,使得执行它的时候能运行一句sql罢了,而接口无法直接调用方法,所以这里使用动态代理生成对象,在执行时还是回到MySqlSession中调用查询,最终由MyExecutor做JDBC查询。这样设计是为了单一职责,可扩展性更强。
三、实现自己的Mybatis工程文件及目录:
首先,新建一个maven项目,在pom.xml中导入以下依赖:
<projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.liugh</groupId><artifactId>liugh-mybatis</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><java.version>1.8</java.version></properties><dependencies><!--读取xml文件--><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><!--MySQL--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.29</version></dependency></dependencies></project>
创建我们的数据库xml配置文件:
<?xmlversion="1.0"encoding="UTF-8"?><database><propertyname="driverClassName">com.mysql.jdbc.Driver</property><propertyname="url">jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8</property><propertyname="username">root</property><propertyname="password">123456</property></database>
然后在数据库创建test库,执行如下SQL语句:
CREATETABLE`user`(`id`varchar(64)NOTNULL,`password`varchar(255)DEFAULTNULL,`username`varchar(255)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=2DEFAULTCHARSET=utf8;INSERTINTO`test`.`user`(`id`,`password`,`username`)VALUES('1','123456','liugh');
创建User实体类,和UserMapper接口和对应的xml文件:
packagecom.liugh.bean;publicclassUser{privateStringid;privateStringusername;privateStringpassword;//省略getsettoString方法...}packagecom.liugh.mapper;importcom.liugh.bean.User;publicinterfaceUserMapper{publicUsergetUserById(Stringid);}<?xmlversion="1.0"encoding="UTF-8"?><mappernameSpace="com.liugh.mapper.UserMapper"><selectid="getUserById"resultType="com.liugh.bean.User">select*fromuserwhereid=?</select></mapper>
基本操作配置完成,接下来我们开始实现MyConfiguration:
packagecom.liugh.sqlSession;importjava.io.InputStream;importjava.sql.Connection;importjava.sql.DriverManager;importjava.sql.SQLException;importjava.util.ArrayList;importjava.util.Iterator;importjava.util.List;importorg.dom4j.Document;importorg.dom4j.DocumentException;importorg.dom4j.Element;importorg.dom4j.io.SAXReader;importcom.liugh.config.Function;importcom.liugh.config.MapperBean;/***读取与解析配置信息,并返回处理后的Environment*/publicclassMyConfiguration{privatestaticClassLoaderloader=ClassLoader.getSystemClassLoader();/***读取xml信息并处理*/publicConnectionbuild(Stringresource){try{InputStreamstream=loader.getResourceAsStream(resource);SAXReaderreader=newSAXReader();Documentdocument=reader.read(stream);Elementroot=document.getRootElement();returnevalDataSource(root);}catch(Exceptione){thrownewRuntimeException("erroroccuredwhileevalingxml"+resource);}}privateConnectionevalDataSource(Elementnode)throwsClassNotFoundException{if(!node.getName().equals("database")){thrownewRuntimeException("rootshouldbe<database>");}StringdriverClassName=null;Stringurl=null;Stringusername=null;Stringpassword=null;//获取属性节点for(Objectitem:node.elements("property")){Elementi=(Element)item;Stringvalue=getValue(i);Stringname=i.attributeValue("name");if(name==null||value==null){thrownewRuntimeException("[database]:<property>shouldcontainnameandvalue");}//赋值switch(name){case"url":url=value;break;case"username":username=value;break;case"password":password=value;break;case"driverClassName":driverClassName=value;break;default:thrownewRuntimeException("[database]:<property>unknownname");}}Class.forName(driverClassName);Connectionconnection=null;try{//建立数据库链接connection=DriverManager.getConnection(url,username,password);}catch(SQLExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}returnconnection;}//获取property属性的值,如果有value值,则读取没有设置value,则读取内容privateStringgetValue(Elementnode){returnnode.hasContent()?node.getText():node.attributeValue("value");}@SuppressWarnings("rawtypes")publicMapperBeanreadMapper(Stringpath){MapperBeanmapper=newMapperBean();try{InputStreamstream=loader.getResourceAsStream(path);SAXReaderreader=newSAXReader();Documentdocument=reader.read(stream);Elementroot=document.getRootElement();mapper.setInterfaceName(root.attributeValue("nameSpace").trim());//把mapper节点的nameSpace值存为接口名List<Function>list=newArrayList<Function>();//用来存储方法的Listfor(IteratorrootIter=root.elementIterator();rootIter.hasNext();){//遍历根节点下所有子节点Functionfun=newFunction();//用来存储一条方法的信息Elemente=(Element)rootIter.next();Stringsqltype=e.getName().trim();StringfuncName=e.attributeValue("id").trim();Stringsql=e.getText().trim();StringresultType=e.attributeValue("resultType").trim();fun.setSqltype(sqltype);fun.setFuncName(funcName);ObjectnewInstance=null;try{newInstance=Class.forName(resultType).newInstance();}catch(InstantiationExceptione1){e1.printStackTrace();}catch(IllegalAccessExceptione1){e1.printStackTrace();}catch(ClassNotFoundExceptione1){e1.printStackTrace();}fun.setResultType(newInstance);fun.setSql(sql);list.add(fun);}mapper.setList(list);}catch(DocumentExceptione){e.printStackTrace();}returnmapper;}}
用面向对象的思想设计读取xml配置后:
packagecom.liugh.config;importjava.util.List;publicclassMapperBean{privateStringinterfaceName;//接口名privateList<Function>list;//接口下所有方法//省略getset方法...}
Function对象包括sql的类型、方法名、sql语句、返回类型和参数类型。
packagecom.liugh.config;publicclassFunction{privateStringsqltype;privateStringfuncName;privateStringsql;privateObjectresultType;privateStringparameterType;//省略getset方法}
接下来实现我们的MySqlSession,首先的成员变量里得有Excutor和MyConfiguration,代码的精髓就在getMapper的方法里。
packagecom.liugh.sqlSession;importjava.lang.reflect.Proxy;publicclassMySqlsession{privateExcutorexcutor=newMyExcutor();privateMyConfigurationmyConfiguration=newMyConfiguration();public<T>TselectOne(Stringstatement,Objectparameter){returnexcutor.query(statement,parameter);}@SuppressWarnings("unchecked")public<T>TgetMapper(Class<T>clas){//动态代理调用return(T)Proxy.newProxyInstance(clas.getClassLoader(),newClass[]{clas},newMyMapperProxy(myConfiguration,this));}}
紧接着创建Excutor和实现类:
packagecom.liugh.sqlSession;publicinterfaceExcutor{public<T>Tquery(Stringstatement,Objectparameter);}
MyExcutor中封装了JDBC的操作:
packagecom.liugh.sqlSession;importjava.sql.Connection;importjava.sql.PreparedStatement;importjava.sql.ResultSet;importjava.sql.SQLException;importcom.liugh.bean.User;publicclassMyExcutorimplementsExcutor{privateMyConfigurationxmlConfiguration=newMyConfiguration();@Overridepublic<T>Tquery(Stringsql,Objectparameter){Connectionconnection=getConnection();ResultSetset=null;PreparedStatementpre=null;try{pre=connection.prepareStatement(sql);//设置参数pre.setString(1,parameter.toString());set=pre.executeQuery();Useru=newUser();//遍历结果集while(set.next()){u.setId(set.getString(1));u.setUsername(set.getString(2));u.setPassword(set.getString(3));}return(T)u;}catch(SQLExceptione){e.printStackTrace();}finally{try{if(set!=null){set.close();}if(pre!=null){pre.close();}if(connection!=null){connection.close();}}catch(Exceptione2){e2.printStackTrace();}}returnnull;}privateConnectiongetConnection(){try{Connectionconnection=xmlConfiguration.build("config.xml");returnconnection;}catch(Exceptione){e.printStackTrace();}returnnull;}}
MyMapperProxy代理类完成xml方法和真实方法对应,执行查询:
packagecom.liugh.sqlSession;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;importjava.util.List;importcom.liugh.config.Function;importcom.liugh.config.MapperBean;publicclassMyMapperProxyimplementsInvocationHandler{privateMySqlsessionmySqlsession;privateMyConfigurationmyConfiguration;publicMyMapperProxy(MyConfigurationmyConfiguration,MySqlsessionmySqlsession){this.myConfiguration=myConfiguration;this.mySqlsession=mySqlsession;}@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{MapperBeanreadMapper=myConfiguration.readMapper("UserMapper.xml");//是否是xml文件对应的接口if(!method.getDeclaringClass().getName().equals(readMapper.getInterfaceName())){returnnull;}List<Function>list=readMapper.getList();if(null!=list||0!=list.size()){for(Functionfunction:list){//id是否和接口方法名一样if(method.getName().equals(function.getFuncName())){returnmySqlsession.selectOne(function.getSql(),String.valueOf(args[0]));}}}returnnull;}}
到这里,就完成了自己的Mybatis框架,我们测试一下:
packagecom.liugh;importcom.liugh.bean.User;importcom.liugh.mapper.UserMapper;importcom.liugh.sqlSession.MySqlsession;publicclassTestMybatis{publicstaticvoidmain(String[]args){MySqlsessionsqlsession=newMySqlsession();UserMappermapper=sqlsession.getMapper(UserMapper.class);Useruser=mapper.getUserById("1");System.out.println(user);}}
执行结果:
查询一个不存在的用户试试:
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。