当前位置: 首页 > 科技观察

如何实现mapper的注册和使用

时间:2023-03-17 19:41:10 科技观察

一、前言如何面对复杂系统的设计?我们可以把大型框架如Spring、Mybatis、Dubbo,或者公司内部的一些核心项目称为复杂系统。这种项目不再是编程初学者手中的玩具项目。没有所谓的CRUD。更多的时候,我们要面对的是系统层次结构的设计和聚合逻辑功能的实现,然后通过层层转换来实现和实施。转移。对于很多刚入门的小码农来说,会觉得很不适应。他们不知道从何下手,却想一口吃掉一个胖子。其实这是不现实的,因为在这些复杂的系统框架中,有太多的内容是你还没有了解和熟悉的。事实上,解决如此复杂的项目问题,核心在于缩小主要问题的范围。具体手段包括:分治法、抽象法和知识法。利用设计模式、设计原则等相关知识,将问题空间合理划分为若干个子问题。问题越小,就越容易理解和处理。就像你可以把很多内容做成一个独立的案例,最后聚合起来使用。2.目标在上一章中,我们初步了解了如何为接口类生成对应的mapper代理,并在代理中完成用户对接口方法的一些调用。虽然我们看到了一个核心逻辑处理方法,但是仍然有一些刀耕火种的操作在使用,包括:需要在代码中告诉MapperProxyFactory代理哪个接口,在实际调用的时候写一个假的SqlSession来处理返回结果界面。那么结合这两个问题,我们将在本章中为mapper注册提供注册机处理,方便用户在使用时提供包路径完成扫描注册。同时需要对SqlSession进行标准化,使其能够包装我们的mapper代理和方法调用,建立生命周期模型结构,方便后续的内容添加。3、设计鉴于我们要将整个工程包下的数据库操作的DAO接口与Mapper映射器关联起来,需要封装一个可以扫描包路径的寄存器类来完成映射。当然,我们还需要对上一章中简化的SqlSession进行完善。SqlSession定义了数据库处理接口和获取Mapper对象的操作,传递给mapper代理类使用。这部分是对上一章的完善。有了SqlSession之后,你可以把它理解为一个函数式服务。当你有了一个功能服务之后,你需要为这个功能服务提供一个工厂来对外提供这样的服务。比如我们在Mybatis中很常见的操作就是开启一个SqlSession。整个设计可以如图3-1所示:图3-1mapper的注册和使用旨在通过封装接口提供mapper的代理类,完成mapper注册机的MapperRegistry,自动扫描接口package和map下各个接口类的代理类都保存在mapper代理的HashMap缓存中。SqlSession和SqlSessionFactory是对这里注册的mapper代理的上层提供的标准定义和对外服务的封装,方便用户使用。我们将用户视为用户。这样封装之后,我们继续扩展框架的功能会更加方便。也希望大家在学习的过程中能够思考这样一个设计结构,能够帮助大家解决一些问题。业务功能开发过程中的领域服务封装。四、实现1、项目结构mybatis-step-02└──src├──main│└──java│└──cn.bugstack.mybatis│├──binding│├──MapperProxy.java││├──MapperProxyFactory.java││└──MapperRegistry.java│└──session│├──defaults││├──DefaultSqlSession.java││└──DefaultSqlSessionFactory.java│├──jaSqlSessionSqlSessionFactory.java└──test└──java└──cn.bugstack.mybatis.test.dao├──dao│├──ISchoolDao.java│└──IUserDao.java└──ApiTest.java项目源码:https://t.zsxq.com/bmqNFQ7。mapper标准定义实现关系,如图3-2所示:图3-2mapper标准定义实现关系MapperRegistry提供包路径扫描和mapper代理类注册服务,完成接口对象的代理类注册过程。SqlSession、DefaultSqlSession用于定义以后执行SQL标准、获取映射器、管理事务的操作。基本上我们平时使用Mybatis的API接口也是从这个接口类定义的方法中使用的。SqlSessionFactory是一个简单的工厂模式,用于提供SqlSession服务,屏蔽创建细节,延迟创建过程。2、MapperRegistry源码参见:cn.bugstack.mybatis.binding.MapperRegistry。publicclassMapperRegistry{/***将添加的映射器代理添加到HashMap*/privatefinalMap,MapperProxyFactory>knownMappers=newHashMap<>();publicTgetMapper(Classtype,SqlSessionsqlSession){finalMapperProxyFactorymapperProxyFactory=(MapperProxyFactory)knownMappers.get(type);}if(mapperProxyFactory==null){thrownewRuntimeException("Type"+type+"不为MapperRegistry所知。");}try{returnmapperProxyFactory.newInstance(sqlSession);}catch(Exceptione){thrownewRuntimeException("获取映射器实例时出错。原因:"+e,e);}}publicvoidaddMapper(Classtype){/*Mapper必须是要注册的接口*/if(type.isInterface()){if(hasMapper(type)){//如果重复添加,会报错thrownewRuntimeException("Type"+type+"已经为MapperRegistry所知。");}//注册映射器代理工厂knownMappers.put(type,newMapperProxyFactory<>(type));}}publicvoidaddMappers(StringpackageName){Set>mapperSet=ClassScanner.scanPackage(packageName);for(ClassmapperClass:mapperSet){addMapper(mapperClass);}}}MapperRegistrymapper注册类的核心主要是提供ClassScanner.scanPackage扫描封装Path,调用addMapper方法,为接口类创建一个MapperProxyFactory映射器代理类,写入到knownMappers的HashMap缓存中,另外该类还提供了对应的getMapper方法获取映射器代理类,其实,这一步包装了我们上一章手动实例化的过程,在DefaultSqlSession中获取Mapper时使用起来更方便3.SqlSession标准定义及实现源码详见:cn.bugstack.mybatis.session.SqlSessio名词publicinterfaceSqlSession{/***获取statementkey映射的单行*根据指定的SqlID获取一条记录的封装对象**@param返回的对象类型封装后的对象类型*@paramstatementsqlID*@returnMappedobject封装对象*/TselectOne(Stringstatement);/***检索从语句键和参数映射的单行。*根据指定的SqlID获取一条记录的封装对象,但是这个方法允许我们给sql传递一些参数*一般在实际使用中,这个参数传递给pojo,或者Map或者ImmutableMap**@param返回的对象类型*@paramstatement与要使用的语句匹配的唯一标识符。*@paramparameter传递给语句的参数对象。*@return映射对象*/TselectOne(Stringstatement,Objectparameter);/***检索映射器。*获取映射器,这个聪明的泛型用于使类型安全**@param映射器类型*@paramtype映射器接口类*@returna映射器boundtothisSqlSession*/TgetMapper(Classtype);}在SqlSession中定义一个标准接口,用于执行SQL,获取mapper对象,以及后续的管理事务操作。目前该接口对数据库的操作只有selectOne提供,其他方法稍后定义。详见源码:cn.bugstack.mybatis.session.defaults。publicclassDefaultSqlSessionimplementsSqlSession{/***MapperRegistry*/privateMapperRegistrymapperRegistry;@OverridepublicTselectOne(Stringstatement,Objectparameter){return(T)("你被代理了!"+"方法:"+语句+"输入参数:"+parameter);}@OverridepublicTgetMapper(Classtype){returnmapperRegistry.getMapper(type,this);}}实现类对SqlSession是通过DefaultSqlSession接口实现的。getMapper方法中获取的mapper对象是通过MapperRegistry类获取的,后面这部分会被配置类替代。在selectOne中,是简单的内容返回,还没有关联到数据库。这部分是在我们逐步开发的过程中逐步实现的。4、SqlSessionFactory工厂定义及实现源码见:cn.bugstack.mybatis.session.SqlSessionFactory。publicinterfaceSqlSessionFactory{/***打开一个session*@returnSqlSession*/SqlSessionopenSession();}这其实是一个简单工厂的定义,它提供了在工厂中实现接口的能力,也就是提供的开口由SqlSessionFactory工厂SqlSession能力。源码详见:cn.bugstack.mybatis.session.defaults.DefaultSqlSessionFactorypublicclassDefaultSqlSessionFactoryimplementsSqlSessionFactory{privatefinalMapperRegistrymapperRegistry;publicDefaultSqlSessionFactory(MapperRegistrymapperRegistry){this.mapperRegistry=mapperRegistry;}@OverridepublicSqlSessionopenSession(){返回新的DefaultSqlSession(mapperRegistry);}}默认的简单工厂实现处理了DefaultSqlSession的创建和SqlSession开启时mapperRegistry的传递,从而在使用SqlSession时可以获取到各个代理类的mapper对象。五、测试1、提前准备在同一个包路径下提供2个以上的Dao接口:publicinterfaceISchoolDao{StringquerySchoolName(StringuId);}publicinterfaceIUserDao{StringqueryUserName(StringuId);IntegerqueryUserAge(StringuId);}2.单元测试@Testpublicvoidtest_MapperProxyFactory(){//1.注册MapperMapperRegistryregistry=newMapperRegistry();registry.addMappers("cn.bugstack.mybatis.test.dao");//2.从SqlSession工厂获取SessionSqlSessionFactorysqlSessionFactory=newDefaultSqlSessionFactory(registry);SqlSessionsqlSession=sqlSessionFactory.openSession();//3.获取映射器对象IUserDaouserDao=sqlSession.getMapper(IUserDao.class);//4.测试验证Stringres=userDao.queryUserName("10001");logger.info("测试结果:{}",res);}在单元测试中通过注册机扫描包路径注册mapper代理对象,并将注册机传递给SqlSessionFactory工厂,从而完成一个链接过程。然后通过SqlSession获取对应DAO类型的实现类,进行方法验证。测试结果22:43:23.254[main]INFOcn.bugstack.mybatis.test.ApiTest-测试结果:您已被代理!方法:queryUserName输入参数:[Ljava.lang.Object;@50cbc42fProcessfinishedwithexitcode0通过测试,可以看到我们已经完成了在一个手写的ORM框架中使用Mybatisshadow代理类的注册和使用。6.总结首先,我们要从设计结构上理解工厂模式对具体功能结构的封装,屏蔽流程细节,限制上下文,降低外部使用的耦合度。从这个过程中读者也可以发现,使用SqlSessionFactory的工厂实现类包装了SqlSession的标准定义实现类,SqlSession完成了mapper对象的注册和使用。本章要注意几个重要的知识点,包括:映射器、代理类、注册机、接口标准、工厂模式、上下文。这些工程开发技巧是手写Mybatis过程中非常重要的部分。只有了解和熟悉,才能更好地运用到自己的业务中。