www.teandq.com
晓安科普

mybatis_mybatis框架

2024-06-01Aix XinLe

简介说到持久层框架,我们很快能想到hibernate、mybatis。Hibernate是全自动的持久层框架,而mybatis则是半自动的。那么

mybatis_mybatis框架

 

简介说到持久层框架,我们很快能想到hibernate、mybatisHibernate是全自动的持久层框架,而mybatis则是半自动的那么两者有何区别?下面先简单认识一下这两个框架的异同,以便更好的理解下面阐述的自定义持久层框架的设计思想,毕竟下面的自定义框架实际就是通过阅读mybatis源码,取其核心设计,去掉细枝旁叶而设计的一个半自动持久层框架。

ORM全称Object/Relation Mapping:表示对象-关系映射的缩写Hibernate:全自动的持久层ORM框架Hibernate是一个开源的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO(Plain Ordinary Java Object)与数据库表建立映射关系,是一个全自动的ORM框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。

Mybatis:MyBatis 是一款优秀的半自动持久层框架,它支持自定义 SQL、存储过程以及高级映射MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO为数据库中的记录。

使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的而 MyBatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 映射工具。

既然无论是全自动的hibernate,或是半自动的mybatis,其本质都是对JDBC进行了封装,使Java程序员可以使用对象编程思维来操纵数据库下面我们不妨看看使用JDBC编程是怎样的,直接使用JDBC编程会有什么问题,找到问题,解决问题,然后提出针对问题的解决方案。

jdbc操作存在的问题一个使用原始jdbc对数据进行查询的案例package com.kmning.wallet.jdbc; import com.kmning.wallet.jdbc.pojo.User;

import java.sql.*; import java.util.ArrayList; import java.util.List; /** * @author kangming.ning *

@date 2021/5/2 9:16 */publicclassJdbcQueryDemo{ publicstaticvoidmain(String[] args){ Connection connection=

null; PreparedStatement preparedStatement=null; ResultSet resultSet =null; try

{ //加载数据库驱动//Class.forName("com.mysql.jdbc.Driver");//过驱动管理类获取数据库链接 connection = DriverManager.getConnection(

"jdbc:mysql://localhost:3306/yourdb?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai", "root", "root"

); //编写sql语句 String sql="select * from user where username=? or telphone=?";

//获取预处理statement preparedStatement = connection.prepareStatement(sql); //设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值

preparedStatement.setString(1,"aa"); preparedStatement.setString(2,"666");

//向数据库发出sql执行查询,查询出结果集 resultSet = preparedStatement.executeQuery(); //遍历结果集,封装数据

List userList=new ArrayList<>(10); while (resultSet.next()){

int id = resultSet.getInt("id"); String username = resultSet.getString("username"); String telphone = resultSet.getString(

"telphone"); //封装结果集 User user = new User(); user.setId(id); user.setUsername(username); user.setTelphone(telphone); userList.add(user); } System.out.println(userList); }

catch (Exception e){ e.printStackTrace(); }finally { //释放资源if (resultSet!=

null){ try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } }

if (preparedStatement!=null){ try { preparedStatement.close(); }

catch (SQLException e) { e.printStackTrace(); } }

if (connection!=null){ try { connection.close(); }

catch (SQLException e) { e.printStackTrace(); } } } } }

代码非常简单且常规,JDBC操作无非都类似于上面的套路通过驱动获取数据库连接,通过PreparedStatement预编译SQL语句,设置参数,然后向数据库发出sql执行查询,查询出结果集,遍历结果集,将结果集封装到POJO对象集合。

当然,最后还得将相关资源释放但在实际的企业应用中,数据库的表少则几十个,多则几百个,如果使用类似于上述的方式去操作数据库,不仅前期开发工作巨大,而且后期的维护也将是一场灾难那么上述代码存在哪些问题?让我们带着疑问对代码进行分析,然后找出问题所在,然后找到解决方案去解决它们。

以上代码存在的问题:数据库配置信息存在硬编码数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能Sql语句在代码中硬编码,造成代码不易维护,实际应用中sql变化的可能较大,sql变动需要改变java代码

使用preparedStatement向占位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便

解决思路:数据库配置信息提供配置文件,避免改动连接等配置信息需要重新编译代码使用数据库连接池初始化连接资源将sql语句、设置参数、获取结果集参数抽取到xml配置文件中使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射

问题找到了,也整理出了解决思路,那么就可以着手设计框架了自定义mybatis框架我们整理出了解决思路,接下来需要编写一个通用的解决方案,也就是框架,去解决问题下面我们整理一下实现这样一个框架需要去做哪些工作。

首先是自定义框架肯定是独立的一个jar,然后提供给客户端使用我们可以将开发工作分为两部分,一是框架需要做哪些东西,二是使用端(项目)需要做哪些东西为了描述与理解方便,下面称框架端为mybatis-custom框架。

mybatis-custom框架负责对jdbc进行封装,提供基本操作接口,并对结果集和pojo实体进行映射,而客户端(项目)则需要提供数据库配置信息、sql配置信息(如UserMapper.xml)使用端提供xxMapper.xml,意味着对sql的编写的控制权还是属于用户的(半自动映射框架)。

使用端提供两部分配置信息:一是数据库配置信息二是sql mapper配置信息,mapper文件定义了sql语句、参数类型、返回值类型、结果返回类型等信息使用配置文件来提供这两部分配置信息sqlMapConfig.xml:存放数据库配置信息、同时存放mapper.xml的全路径(这样在方便在解析sqlMapConfig时直接解析mapper.xml,相关信息保存到Configuration)。

样例如下

>

>

>

>自定义持久层框架端创建框架工程,引入相关maven依赖,根据上面的分析,框架需要解析xml,用连接池解决连接创建关闭频繁问题,那么引入相关依赖如下

>dom4jdom4j1.6.1

>jaxenjaxen1.2.0

>mysqlmysql-connector-java8.0.22

>com.alibabadruid1.1.20

>根据自定义的核心配置文件sqlMapConfig.xml和userMapper.xml文件可以看出,核心配置文件里面有连接池信息,mapper信息,那么解析成核心配置类后也应该记录这些信息,方便后面使用。

而userMapper.xml记录了数据表的sql操作的配置文件肯定也要映射成某个类, 我们根据上面定义的userMapper.xml配置信息抽象成成类即可使用端已经使用数据库xml配置文件l和SQL xml配置文件来解决相关的硬编码问题,接下来就是框架端的工作了。

框架端本质就是需要对JDBC代码进行封装框架端解析客户端提供的数据库配置文件以创建数据库连接,当然,这里我们使用数据库连接池来解决频繁创建、释放连接问题框架端解析xxMapper.xml配置文件,以将每个Mapper.xml文件对应的SQL标签解析成特定的配置对象(比如叫MappedStatement),最后我们把这些解析出来的配置对象保存到一个全局的配置类中(比如叫Configuration)。

这个配置类在mybatis-custom中起到了基石的作用,毕竟这个框架最终是要操作数据库的,而这个配置类对象为我们提供了数据库操作的相关信息Configurationpackage com.kmning.mybatis.config;

import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @author kangming.ning *

@date 2021/5/2 17:18 */publicclassConfiguration{ /** * 保存连接池信息 * */private DataSource dataSource;

/** * key: namespace+id,唯一标识一条sql * value:MappedStatement对象 通过key找到当前业务表的mapper,所以在定义dao接口时,接口的方法全限定名称 * 和其对应的mapper中的namespace+id 必须是一致的,框架会根据当前的查询方法的全限定方法名称去找到对应的MappedStatement * 对象 * 指定初始Map大小,默认16 * */

private Map mappedStatementMap=new HashMap<>(100); public DataSource getDataSource

(){ return dataSource; } publicvoidsetDataSource(DataSource dataSource){ this

.dataSource = dataSource; } public Map getMappedStatementMap(){

return mappedStatementMap; } }可以看出,核心配置类主要保存连接池信息、另外用一个Map保存了所有数据库操作信息(简单的理解就是Mapper.xml里面的SQL标签对应的对象),其中key是namespace+id,唯一标识一条sql。

namespace(Mapper.xml中定义的)就是我们定义的DAO接口全限定名,id则是具体的接口名称这样一来,当我们调用DAO某个接口时,框架就能找到调用接口对应的Mapper.xml文件对应的SQL标签(或者说MappedStatement对象),从而解析出需要执行的SQL。

到此,我们已经准备好了框架执行SQL的一切条件,接下来应该要交给PreparedStatement去执行,然后得到结果,对结果进行封装等我们定义一个执行器接口,提供查询、增删查改接口Executor接口。

定义一个执行器接口,负责和数据库进行交互,提供查询、增删查改接口package com.kmning.mybatis.sqlSession; import com.kmning.mybatis.config.Configuration; 。

import com.kmning.mybatis.config.MappedStatement; ​ import java.util.List; ​ /** * 定义数据库查询、更新接口 * @author

kangming.ning * @date 2021/5/2 20:33 */publicinterfaceExecutor{ ​ /** * 查询接口,使用jdbc与数据通讯,封装数据映射到pojo类返回 *

@param configuration 核心配置对象 * @param mappedStatement 封装了一条sql相关信息的对象 * @param params sql入参对象 *

@return 已自动映射成pojo类的查询结果 * */ List query(Configuration configuration, MappedStatement mappedStatement, Object... params)

throws Exception; ​ /** * 更新接口,使用jdbc与数据通讯,封装数据映射到pojo类返回 * @param configuration 核心配置对象 *

@param mappedStatement 封装了一条sql相关信息的对象 * @param params sql入参对象 * @return 影响行数 * */intupdate

(Configuration configuration, MappedStatement mappedStatement,Object... params)throws Exception; }接口定义了一个列表查询方法,一个更新方法,然后需要传递核心配置对象与MappedStatement对象,这样才能根据配置信息解析出要执行的SQL,将参数设置到PreparedStatement。

在查询接口中,要求返回一个List,查询结果需要封装到泛型列表对象进行返回从上面的Mapper.xml的定义样例中我们大概能猜到这个接口的实现需要做的事情,比如根据MappedStatement解析SQL、设置SQL参数、查询结果,通过反射或内省将数据库查询结果映射到Java对象中。

更新接口也是做差不多的事情,只是不需要映射结果集到对象,只需要返回影响行数即可MappedStatement将userMapper.xml抽象成MappedStatement类,用于记录相关信息,命名空间加相关查询标签的id属性可以唯一确定一个sql,另外入参类型,结果类型,标签里面的sql文本为重要信息,应该抽象成字段。

package com.kmning.mybatis.config; /** * 对某个mapper的一条sql作封装,即系统有多少条sql,将会产生多少个MappedStatement对象 * 注意下面的属性名称应该和mapper.xml定义的sql标签的属性对应起来,方便理解的同时也规范 * @author kangming.ning * @date 2021/5/2 17:05 */

publicclass MappedStatement { /** * 唯一标签某个mapper的某条sql,用namespace+id,方便针对不同表映射到不同的mapper.xml * 总不能把所有表放同一个mapper吧 * */

privateString id; /** * 入参类型全限定类路径 * */privateString parameterType; /** * 结果类型全限定类路径 * */

privateString resultType; /** * 标签里面的sql语句,此语句后续需要进一步解析(如自定义入参标签#{}等) * */privateString

sql; //get set方法 ... } SimpleExecutorpackage com.kmning.mybatis.sqlSession; import com.kmning.mybatis.config.BoundSql;

import com.kmning.mybatis.config.Configuration; import com.kmning.mybatis.config.MappedStatement; import

com.kmning.mybatis.utils.GenericTokenParser; import com.kmning.mybatis.utils.ParameterMapping; import

com.kmning.mybatis.utils.ParameterMappingTokenHandler; import java.beans.PropertyDescriptor; import

java.lang.reflect.Field; import java.lang.reflect.Method; import java.sql.*; import java.util.ArrayList;

import java.util.List; /** * @author kangming.ning * @date 2021/5/2 20:46 */publicclassSimpleExecutor

implementsExecutor{ @Overridepublic List query(Configuration configuration, MappedStatement mappedStatement, Object... params)

throws Exception{ PreparedStatement preparedStatement = getPreparedStatement(configuration, mappedStatement, params); ResultSet resultSet = preparedStatement.executeQuery();

//封装结果集 String resultType = mappedStatement.getResultType(); Class resultClassType = getClassType(resultType);

//遍历结果集封装到resultType对应的pojo类对象中 List resultList=new ArrayList<>(10); while (resultSet.next()){ Object resultObj = resultClassType.newInstance();

//通过元数据找到字段名和其对应的值 这样才能准确的设置到返回类型对象对应的属性中 ResultSetMetaData metaData = resultSet.getMetaData();

//注意是从第一列开始for (int i = 1; i <= metaData.getColumnCount(); i++) { String columnName = metaData.getColumnName(i); Object columnValue = resultSet.getObject(columnName);

//使用反射或者内省,根据数据库表和实体的对应关系,完成封装//使用内省(Introspector) PropertyDescriptor propertyDescriptor =

new PropertyDescriptor(columnName,resultClassType); //如setUsername Method writeMethod = propertyDescriptor.getWriteMethod();

//调用setter方法设置对象当前属性的值 writeMethod.invoke(resultObj,columnValue); //使用反射(反射和内省,选一种即可)

/* Field declaredField = resultClassType.getDeclaredField(columnName); declaredField.setAccessible(true); declaredField.set(resultObj,columnValue);*/

} resultList.add(resultObj); } return (List) resultList; }

@Overridepublicintupdate(Configuration configuration, MappedStatement mappedStatement, Object... params)

throws Exception{ PreparedStatement preparedStatement = getPreparedStatement(configuration, mappedStatement, params); preparedStatement.execute();

int updateCount = preparedStatement.getUpdateCount(); return updateCount; } /** * 通用获取PreparedStatement方法 * */

private PreparedStatement getPreparedStatement(Configuration configuration, MappedStatement mappedStatement, Object... params)

throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { //通过核心配置获取连接池中的一个连接

Connection connection = configuration.getDataSource().getConnection(); //获取sql 获取sql语句 : select * from user where id = #{id} and username = #{username}

String sql = mappedStatement.getSql(); //转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储

BoundSql boundSql = getBoundSql(sql); //获取预处理对象:preparedStatement PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

//设置参数//获取参数的全路径 String parameterType = mappedStatement.getParameterType(); //获取入参Class对象

Class paramClassType = getClassType(parameterType); //根据通过反射获取入参对象中对应了 #{id}中的id和#{username}的username字段的值,设置到preparedStatement

//这个列表是有序的,比如示例sql中,下面列表第0个元素就是id,第1一个元素就是username List parameterMappingList = boundSql.getParameterMappingList();

for (int i = 0; i < parameterMappingList.size(); i++) { ParameterMapping parameterMapping = parameterMappingList.get(i);

//其实保存的就是入参对象的字段名 String filedName = parameterMapping.getContent(); //反射获取入参对象当前字段的值

Field declaredField = paramClassType.getDeclaredField(filedName); //忽视修饰符号 直接访问

declaredField.setAccessible(true); //注意下面的params[0]其实就是入参对象 如User对象,根据这个对象直接获取到了对象的值

Object fieldValue = declaredField.get(params[0]); //设置preparedStatement的入参 第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值

preparedStatement.setObject(i+1,fieldValue); } return preparedStatement; }

/** * 完成对#{}的解析工作:1.将#{}使用?进行代替,2.解析出#{}里面的值进行存储 * @param sql 如 select * from user where id = #{id} and username = #{username} *

@return */private BoundSql getBoundSql(String sql){ //标记处理类:配置标记解析器来完成对占位符的解析处理工作 ParameterMappingTokenHandler parameterMappingTokenHandler =

new ParameterMappingTokenHandler(); GenericTokenParser genericTokenParser = new GenericTokenParser(

"#{", "}", parameterMappingTokenHandler); //解析出来的sql select * from user where id = ? and username = ?

String parseSql = genericTokenParser.parse(sql); //#{}里面解析出来的参数名称 id username 通过取入参的查询对象的id和username值分别设置为第一个?和第二个?的参数值进行查询(结果是有序的)

List parameterMappings = parameterMappingTokenHandler.getParameterMappings(); BoundSql boundSql =

new BoundSql(parseSql,parameterMappings); return boundSql; } /** * 根据class全路径反射获取Class对象 * */

private Class getClassType(String clazzPath) throws ClassNotFoundException { if (null!=clazzPath){ Class aClass = Class.forName(clazzPath);

return aClass; } returnnull; } }这个执行器是自动映射的关键当调用查询接口时,框架使用核心配置对象,将用户传进来的参数对象,通过反射获取值,配合Mapper.xml解析出的内容给SQL设置参数,执行后将结果集自动映射到resultType对象,最终返回结果列表。

这里的参数值获取和结果数据映射都使用了反射或内省技术,很好的解决了硬编码等问题反射,程序员的快乐至此,我们基本上完成了框架的配置文件解析(框架初始化时解析到configuration对象)、SQL参数设置、执行结果解析封装。

接下来我们需要提供一个会话对象给用户使用,提供增删查改等接口这样用户只需要知道这个会话对象怎么使用的就行,不必理会其内部是如何实现的SqlSessionpackage com.kmning.mybatis.sqlSession; import java.util.

List; /** * sql会话接口,定义sql基本接口 * @author kangming.ning * @date 2021/5/2 18:58 */publicinterfaceSqlSession

{ /** * 根据statementId查询数据列表 * @param statementId mapper.xml中唯一标识一条sql * @param params 查询参数 * */

List selectList(String statementId, Object... params) throws Exception; /** * 根据statementId查询一条数据 *

@param statementId mapper.xml中唯一标识一条sql * @param params 查询参数 * */ T selectOne(String statementId,Object... params) throws

Exception; /** * 根据statementId 更新、新增、删除数据 * @param statementId mapper.xml中唯一标识一条sql *

@param params 查询参数 * */ Integer excuteUpdate(String statementId, Object... params) throws Exception

; /** * 为Dao接口生成动态代理类 * */ T getMapper(Class mapperClass); }从接口的声明中可以看出,用户只需要知道什么是statementId,然后传SQL参数进去即可,至于内部如何实现则完全不需要关心。

DefaultSqlSessionpackage com.kmning.mybatis.sqlSession; import com.kmning.mybatis.config.Configuration;

import com.kmning.mybatis.config.MappedStatement; import java.lang.reflect.*; import java.util.List;

/** * 默认SqlSession实现 * @author kangming.ning * @date 2021/5/2 19:36 */publicclass DefaultSqlSession

implements SqlSession{ private Configuration configuration; private Executor executor;

public DefaultSqlSession(Configuration configuration) { this.configuration = configuration; executor=

new SimpleExecutor(); } @Overridepublic List selectList(String statementId, Object... params) throws Exception { MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); List resultList = executor.query(configuration, mappedStatement, params);

return resultList; } @Overridepublic T selectOne(String statementId, Object... params) throws Exception { List<

Object> results = selectList(statementId, params); if (results.size()==1){ return

(T) results.get(0); }else { thrownew RuntimeException("查询结果为空或者返回结果过多"); } }

@Overridepublic Integer excuteUpdate(String statementId, Object... params) throws Exception { MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); int update = executor.update(configuration, mappedStatement, params);

return update; } @Overridepublic T getMapper(Class mapperClass) { Object mapperObj = Proxy.newProxyInstance(mapperClass.getClassLoader(),

new Class[]{mapperClass}, new InvocationHandler() { @OverridepublicObject invoke(Object proxy, Method method,

Object[] args) throws Throwable { // 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne// 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名

// 方法名:findAllString clazzName = method.getDeclaringClass().getName(); String methodName = method.getName();

String statementId=clazzName+"."+methodName; // 准备参数2:params:args// 获取被调用方法的返回值类型 Type genericReturnType = method.getGenericReturnType();

//判断是否进行了 泛型类型参数化if (genericReturnType instanceof ParameterizedType){ List

> selectList = selectList(statementId, args); return selectList; }

//处理添加、修改、删除的情况 MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);

String sql = mappedStatement.getSql(); if (sql.startsWith("update")||sql.startsWith(

"insert") ||sql.startsWith("delete")|| sql.startsWith("UPDATE"

)||sql.startsWith("INSERT") ||sql.startsWith("DELETE")){ Integer count = excuteUpdate(statementId, args);

return count; } return selectOne(statementId,args); } });

return (T) mapperObj; } }可以看出,实现方法无非是根据statementId将MappedStatement对象从框架初始化时就已经存下来的Map中取出,然后调用Executor对象去执行,得到结果。

这些步骤对于不同的SQL查询基本都是一样的,所以不必让用户去写,提供接口即可当然,细心的读者可能注意到上面定义了一个getMapper接口并且给出了实现代码其实这就是mybatis的代理模式的实现原理mybatis的代理是使用JDK的动态代理进行实现的,使用动态代理用户就没必要对Dao接口进行实现,因为其实现代码都是大同小异,甚至可以认为是基本重复的,在文末的测试类中我会给出一个Dao实现类,读者可以观察其实现代码。

SqlSessionFactory有了Sqlsession操作接口及实现就已经可以使用Sqlsession对象去和数据库进行交互了但使用Sqlsession是需要在构造函数里传入configuration对象的。

不可能由客户端自己去创建这个对象所以我们需要一个生产Sqlsession对象的工厂接口,将configuration对象保存在工厂类里面另外在工厂Builder里面提供创建工厂对象方法通常系统只需要创建一次即可,有了工厂生产Sqlsession对象就比较方便了。

package com.kmning.mybatis.sqlSession; /** * 定义生产SqlSession接口 * @author kangming.ning * @date 2021/5/2 22:44 */

publicinterfaceSqlSessionFactory{ /** * 获取一个sqlsession会话对象 * */SqlSession openSession()

; }DefaultSqlSessionFactory/** * 默认工厂实现 * @author kangming.ning * @date 2021/5/2 22:45 */publicclass

DefaultSqlSessionFactoryimplementsSqlSessionFactory{    private Configuration configuration;    public

DefaultSqlSessionFactory(Configuration configuration){        this.configuration = configuration;   }    

@Overridepublic SqlSession openSession(){        returnnew DefaultSqlSession(configuration);   } }

会话对象解决了,但核心配置类还是需要传进来这是必然的,因为这个配置类是框架的核心,并且依靠用户的配置文件来生成所以必须要有个接口能让用户传配置文件的流进来,然后使用解析类去解析配置文件,封装到核心配置文件里,然后就可以将核心配置对象传进工厂里面生产会话对象进行使用了。

SqlSessionFactoryBuilderpackage com.kmning.mybatis.sqlSession; import com.kmning.mybatis.config.Configuration;

import com.kmning.mybatis.parser.XMLConfigBuilder; import org.dom4j.DocumentException; import java.io.InputStream;

/** * 构建SqlSessionFactory默认实现工厂对象 * @author kangming.ning * @date 2021/5/2 22:52 */publicclassSqlSessionFactoryBuilder

{ publicstatic SqlSessionFactory build(InputStream inputStream)throws DocumentException {

//第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中 XMLConfigBuilder xmlConfigBuilder=new XMLConfigBuilder(); Configuration configuration = xmlConfigBuilder.parseConfig(inputStream);

//第二:创建sqlSessionFactory对象:工厂类:生产sqlSession:会话对象 DefaultSqlSessionFactory defaultSqlSessionFactory =

new DefaultSqlSessionFactory(configuration); return defaultSqlSessionFactory; } }上面的XMLConfigBuilder就是用来解析核心配置文件以将相关信息保存到核心配置对象中的。

基本这个方法就是提供给用户使用的了框架端开发流程整理读取配置文件:读取完成以后以流的形式存在,我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可以创建javaBean来存储(1)Configuration : 核心配置类(sqlMapConfig.xml解析出来的内容),存放数据库基本信息、Map 唯一标识:namespace + "." +id。

(2)MappedStatement:sql语句、statement类型、输入参数java类型、输出参数java类型解析配置文件创建sqlSessionFactoryBuilder类:方法:sqlSessionFactory build():

第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration和MappedStatement中第二:创建SqlSessionFactory的实现类DefaultSqlSession

创建SqlSessionFactory方法:openSession() : 获取sqlSession接口的实现类实例对象创建sqlSession接口及实现类:主要封装crud方法方法:selectList(String statementId,Object param):查询所有

selectOne(String statementId,Object param):查询单个具体实现:封装JDBC完成对数据库表的查询操作涉及到的设计模式:Builder构建者设计模式、工厂模式、代理模式。

使用端测试自定义框架引入相关依赖com.kmning.mybatismybatis-custom

>1.0-SNAPSHOTjunitjunit

>4.13.2UserMapper.xmlnamespace+id可以标识一条sql

>

> select * from user

parameterType="com.kmning.mybatis.test.pojo.User"> select * from user where id = #{id} and username = #{username}

> UPDATE `user` SET username=#{username},telphone=#{telphone} WHERE id=#{id};

id="insertUser"resultType="java.lang.Integer"parameterType="com.kmning.mybatis.test.pojo.User"> INSERT INTO `user` VALUES(#{id},#{username},#{telphone});

> DELETE FROM `user` WHERE id=#{id}; User实体用于映射数据库查询记录package com.kmning.mybatis.test.pojo;

/** * @author kangming.ning * @date 2021/5/2 16:43 */publicclassUser{ private Integer id;

private String username; private String telphone; // get set ... }dao接口package com.kmning.mybatis.test.dao;

import com.kmning.mybatis.test.pojo.User; import java.util.List; /** * @author kangming.ning * @date

2021/5/2 18:11 */publicinterfaceUserDao{ List findAll()throws Exception; User findByCondition

(User user)throws Exception; Integer insertUser(User user)throws Exception; Integer updateUserById

(User user)throws Exception; Integer deleteUserById(User user)throws Exception; }创建dao接口实现类package

com.kmning.mybatis.test.dao; ​ import com.kmning.mybatis.io.Resources; import com.kmning.mybatis.sqlSession.SqlSession;

import com.kmning.mybatis.sqlSession.SqlSessionFactory; import com.kmning.mybatis.sqlSession.SqlSessionFactoryBuilder;

import com.kmning.mybatis.test.pojo.User; import org.dom4j.DocumentException; import java.io.InputStream;

import java.util.List; /** * 数据层实现类 * @author kangming.ning * @date 2021/5/2 22:58 */publicclass

UserDaoImplimplementsUserDao{ private SqlSessionFactory sessionFactory; publicUserDaoImpl()

{ InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); try

{ sessionFactory = SqlSessionFactoryBuilder.build(resourceAsStream); } catch (DocumentException e) { e.printStackTrace(); } }

public List findAll()throws Exception { SqlSession sqlSession = sessionFactory.openSession(); List userList = sqlSession.selectList(

"com.kmning.mybatis.test.dao.UserDao.findAll"); return userList; } public User findByCondition

(User user)throws Exception { SqlSession sqlSession = sessionFactory.openSession(); User result = sqlSession.selectOne(

"com.kmning.mybatis.test.dao.UserDao.findByCondition",user); return result; } public

Integer insertUser(User user)throws Exception { SqlSession sqlSession = sessionFactory.openSession(); Integer rows = sqlSession.excuteUpdate(

"com.kmning.mybatis.test.dao.UserDao.insertUser", user); return rows; } public Integer

updateUserById(User user)throws Exception { SqlSession sqlSession = sessionFactory.openSession(); Integer rows = sqlSession.excuteUpdate(

"com.kmning.mybatis.test.dao.UserDao.updateUserById", user); return rows; } public Integer

deleteUserById(User user)throws Exception { SqlSession sqlSession = sessionFactory.openSession(); Integer rows = sqlSession.excuteUpdate(

"com.kmning.mybatis.test.dao.UserDao.deleteUserById", user); return rows; } }测试类package com.kmning.mybatis.test;

import com.kmning.mybatis.test.dao.UserDao; import com.kmning.mybatis.test.dao.UserDaoImpl; import com.kmning.mybatis.test.pojo.User;

import org.junit.Test; import java.util.List; /** * @author kangming.ning * @date 2021/5/2 23:15 */

publicclassUserTest{ private UserDao userDao=new UserDaoImpl(); @TestpublicvoidfindAll()throws

Exception { List list = userDao.findAll(); for (User user : list) { System.out.println(user); } }

@TestpublicvoidfindByCondition()throws Exception { User user = new User(); user.setUsername(

"aa"); user.setId(1); User byCondition = userDao.findByCondition(user); System.out.println(byCondition); }

@TestpublicvoidinsertOne()throws Exception { User user = new User(); user.setId(4); user.setUsername(

"cc"); user.setTelphone("666"); Integer count = userDao.insertUser(user); System.out.println(

"影响行数:"+count); } @TestpublicvoidupdateOne()throws Exception { User user = new User(); user.setId(

4); user.setUsername("ccc"); user.setTelphone("6667"); Integer count = userDao.updateUserById(user); System.out.println(

"影响行数:"+count); } @TestpublicvoiddeleteOne()throws Exception { User user = new User(); user.setId(

4); Integer count = userDao.deleteUserById(user); System.out.println("影响行数:"+count); } }

使用Mapper(动态)代理模式开发通过上述我们的自定义框架,我们解决了JDBC操作数据库带来的一些问题:例如频繁创建释放数据库连接,硬编码,手动封装返回结果集等问题,但是现在我们继续来分析刚刚完成的自定义框架代码,有没有什么问题?

问题如下 :dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调用sqlsession方法,关闭 sqlsession)dao的实现类中存在硬编码,调用sqlsession的方法时,参数statement的id硬编码

解决:使用代理模式来创建接口的代理对象在SqlSession的实现类(DefaultSqlSession)中实现获取代理对象的方法@Overridepublic T getMapper(Class mapperClass) {

Object mapperObj = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new

InvocationHandler() { @OverridepublicObject invoke(Object proxy, Method method, Object[] args) throws Throwable {

// 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne// 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名

// 方法名:findAllString clazzName = method.getDeclaringClass().getName(); String methodName = method.getName();

String statementId=clazzName+"."+methodName; // 准备参数2:params:args// 获取被调用方法的返回值类型 Type genericReturnType = method.getGenericReturnType();

//判断是否进行了 泛型类型参数化if (genericReturnType instanceof ParameterizedType){ List

> selectList = selectList(statementId, args); return selectList; }

//处理添加、修改、删除的情况 MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);

String sql = mappedStatement.getSql(); if (sql.startsWith("update")||sql.startsWith(

"insert") ||sql.startsWith("delete")|| sql.startsWith("UPDATE"

)||sql.startsWith("INSERT") ||sql.startsWith("DELETE")){ Integer count = excuteUpdate(statementId, args);

return count; } return selectOne(statementId,args); } });

return (T) mapperObj; }测试代理模式下的CRUDpackage com.kmning.mybatis.test; ​ import com.kmning.mybatis.io.Resources;

import com.kmning.mybatis.sqlSession.SqlSession; import com.kmning.mybatis.sqlSession.SqlSessionFactory;

import com.kmning.mybatis.sqlSession.SqlSessionFactoryBuilder; import com.kmning.mybatis.test.dao.UserDao;

import com.kmning.mybatis.test.pojo.User; import org.dom4j.DocumentException; import org.junit.Before;

import org.junit.Test; import java.io.InputStream; import java.util.List; /** * @author kangming.ning *

@date 2021/5/2 23:15 */publicclassUserMapperTest{ private UserDao userDao; @Beforepublic

voidbefore()throws DocumentException { InputStream resourceAsStream = Resources.getResourceAsStream(

"sqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); userDao= sqlSession.getMapper(UserDao

.class); } @TestpublicvoidfindAll()throws Exception { List list = userDao.findAll();

for (User user : list) { System.out.println(user); } } @TestpublicvoidfindByCondition

()throws Exception { User user = new User(); user.setUsername("aa"); user.setId(

1); User byCondition = userDao.findByCondition(user); System.out.println(byCondition); }

@TestpublicvoidinsertOne()throws Exception { User user = new User(); user.setId(4); user.setUsername(

"cc"); user.setTelphone("666"); Integer count = userDao.insertUser(user); System.out.println(

"影响行数:"+count); } @TestpublicvoidupdateOne()throws Exception { User user = new User(); user.setId(

6); user.setUsername("ccc"); user.setTelphone("6667"); Integer count = userDao.updateUserById(user); System.out.println(

"影响行数:"+count); } @TestpublicvoiddeleteOne()throws Exception { User user = new User(); user.setId(

4); Integer count = userDao.deleteUserById(user); System.out.println("影响行数:"+count); } }

至此,已经基本完整介绍了自定义框架的思路并对其进行实现。当然,忽略不了不少非核心代码,比如解析配置文件等代码,相对简单,避免文章过于拖沓冗长(可能已经冗长了)就没有全部贴出来。​

免责声明:本站所有信息均搜集自互联网,并不代表本站观点,本站不对其真实合法性负责。如有信息侵犯了您的权益,请告知,本站将立刻处理。联系QQ:1640731186

知识mybatis_mybatis框架

2024-06-01Aix XinLe137

mybatis_mybatis框架简介说到持久层框架,我们很快能想到hibernate、mybatis。Hibernate是全自动的持久层框架,而mybatis则是半自动的。那么…

知识新加坡_新加坡留学一年到底要花多少钱

2024-06-01Aix XinLe27

新加坡_新加坡留学一年到底要花多少钱新加坡作为亚洲教育水平的引领者,具有完善的教育体系和顶尖的教育资源,再加上治安稳定安全及其低廉的生活消费成为了不少中国学生留学读书的首选,因而成…

历史赌神之人鬼合一_赌神之人鬼合一完整版

2024-06-01Aix XinLe6

赌神之人鬼合一_赌神之人鬼合一完整版这座义庄已有数百年的历史,据说在很久以前这里曾是一个繁华的村落。但由于一场突如其来的瘟疫,村子里的居民纷纷染病,最后只剩下了一名年轻的道士和一些…

知识哈尔滨1944_哈尔滨1944电视剧免费观看完整版

2024-06-01Aix XinLe55

哈尔滨1944_哈尔滨1944电视剧免费观看完整版《哈尔滨1944》是一部以抗日战争时期为背景的谍战题材电影,通过宋卓文这一角色的视角,展现了共产党情报人员在敌后进行情报搜集和斗争的艰难与智慧。…

历史比乐阁小说网_比乐贴吧

2024-06-01Aix XinLe66

比乐阁小说网_比乐贴吧他点了点头,道:“是,后来我父亲由于她们家族的的压迫,不得不给他们个交代,可任我怎么解释,他们都不会相信。他们最终要求要把我实施鞭刑为条件,才肯…