-
拦截器实现分页3
需要从被拦截的对象中获取原始的sql语句和配置参数。拦截器类实现的intercept方法参数中的Invocation对象中就有被拦截下来的对象(也就是实现了StatementHandler接口的对象,由于拦截的对象有很多种,但是拦截下来的对象是确定的StatementHandler,所以这里通过getTarget()方法获得的对象需强转为StatementHandler类型)。
Mybatis的Plugin类提供的wrap()方法,拦截的如果不是需要的对象
,该方法返回的是原对象本身,从这以后该对象就和分页拦截器没有关系了。如果拦截的是需要的对象,则会返回代理类,并执行intercept方法
查看全部 -
拦截器实现分页2
使用拦截器实现分页的前提需要有一个执行查询的方法,这里在serviceImpl中就不再调用查询所有记录的方法了(原来调用查询所有是要计算出Page的其他属性),这里只是把Page参数传入到Dao中,在配置文件sql中定义的查询是查询所有的记录并没有实现分页功能的。无论是自己实现还是拦截器实现分页都需要传入Page对象到配置文件sql中。
定义拦截器:拦截器根据功能的不同,可以不止一个,创建拦截器的包。
在该包下创建拦截器类,命名为PageInterceptor,Mybatis提供了拦截的操作,可以实现分页的功能,所以要实现Mybatis的接口Interceptor(该接口是org.apache.ibatis.plugin.Interceptor包下的)。
首先该拦截器需要对Mybatis的xml文件中执行的指定的sql语句进行拦截,并转换为分页的sql语句(拦截的时间是在获得PreparedStatement对象之前,拦截的地点是在Mybatis源码中)。
Mybatis获取PreparedStatement对象是在StatementHandler接口(org.apache.ibatis.executor.statement)中,该接口中的prepare方法返回的是Statement对象。
该接口的实现类有两个(BaseStatementHandler、RoutingStatementHandler),该接口的实现类是BaseStatementHandler中,statement是通过该方法中的instantiateStatement(connection)获取到的。
instantiateStatement方法是一个抽象方法(BaseStatementHandler接口也有三个实现类CallableStatementHandler、PreparedStatementHandler、SimpleStatementHandler),该方法是在PreparedStatementHandler实现类中实现的,如下图就和JDBC中相似了,拦截器的拦截位置就是在这里。
拦截器是通过Mybatis提供的注解来拦截到该位置:也就是在拦截器类的上添加@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class})}),type指的是拦截接口的class,method指向拦截的方法。args指的是拦截方法中需要传入参数的类类型。
这样该类对应的实现代码,在执行sql语句之前就可对源码的功能进行修改,因为该实现代码在执行之前已经获取到sql语句,该实现代码就会修改该sql语句并返回给Mybatis。
拦截器类实现的接口需要实现三个方法:
plugin方法需要传入一个Object参数,如果该对象符合拦截的规则(规则就是拦截器类上的注解信息),就会返回一个代理,该代理就可以使用分页的功能,这个代理通过Plugin.wrap(Object对象,this)方法返回,this指的是当前拦截器实例。
可以通过查看wrap方法的源码,它是由Mybatis实现的,该方法调用getSignatureMap(拦截器实例)
getSignatureMap(Interceptor interceptor)方法通过拦截器实例获取到拦截器类上写的注解的信息(该注解的作用在于就在于这里),通过这个注解就可以找到拦截的类型
如果传入的对象符合了拦截器的规则,就会执行拦截器的功能,修改相应的sql语句。通过interceptor(Invocation invocation)方法。如果不符合规则就不会执行interceptor方法,因为没有获取到代理类。
查看全部 -
拦截器实现分页
实际开发中,会有很多查询列表页面需要分页功能,这时分页的简单实现就不再适用,这时就需要把分页功能的共通代码分离出来,以供其他需要分页的地方使用。
【1】页面的jsp和js方法的封装(完成跳转到后台)
【2】sql的xml文件的sql语句进行封装(因为要传入Limit的参数,这里定义了两条几乎同样的sql语句,只不过是一条通过count(*)返回所有记录数,这里需要把查询所有sql语句封装)
【3】配置文件sql语句后的Limit和起始位置和返回数量的封装
【4】页面可以通过自定义标签来解决,这样每个需要分页的地方直接引入该标签即可。
原始的JDBC中Dao层的封装:
开发一个共通的方法,得到Connection对象,得到查询的sql语句,该共通方法可以根据sql语句查询出总条数(该sql语句无需截断再拼接count(*),这样容易出错。可以把该sql语句当作子查询,外面再嵌套一层sql语句),这样Page的属性就都有值了,就可以再Dao的sql语句后拼接Limit参数了。
Mybatis中Dao层的封装:
该封装的关键是sql语句在配置文件里,配置文件貌似获取不到sql参数。
Mybatis拦截器:Mybatis拦截器提供为所有页面实现分页的功能,对拦截到的都会实现分页功能(实现原理:就是拦截查询的sql语句并把它修改为分页查询的sql语句)。
在没有使用拦截器时,在执行sql之前拦截,并调用分页共通,修改的是Mybatis的源码。所以提供了拦截器,在不改变Mybatis源码的同时,可以修改源码的行为。
Mybatis执行sql语句简化版的过程:它的原理和JDBC相似,根据sql语句得到PreparedStatement对象,为该对象setObject,再执行execute方法,最后得到结果,Mybatis拦截器就是相当于在传入sql语句获得PreparedStatement对象前,修改它为分页的sql语句。
查看全部 -
Mybatis 的特点
1)SQL语句与代码分离
优点:便于管理和维护
缺点:不便于调试,需要借助日志工具获得信息2)用标签控制动态SQL的拼接
优点:用标签代替编写逻辑代码
缺点:拼接复杂SQL语句时,没有代码灵活,比较复杂3)结果集与Java对象的自动映射
优点:保证名称相同即可自动映射
缺点:对开发人员所写的SQL依赖性很强4)编写原生SQL(半自动)
优点:接近JDBC,很灵活
缺点:对SQL语句依赖程度很高,数据库一直不方便查看全部 -
分页的简单实现
重点关注分页功能中Mybatis部分的内容(观察Mybatis的介入和之前的分页有何不同)
该分页功能的参数:
1、记录总数。
2、每页显示记录数。
3、总页数。
4、当前页。
5、分页查询Limit的起始点。
6、分页查询Limit的返回个数。
通常封装一个类来实现分页功能:
Mysql使用Limit进行分页。Limit关键字需要两个参数,一个是从第几条开始取,另一个是取多少条。
该类中还封装了计算总页数的方法,并且可以计算出Limit需要的两个参数值,然后放到该类对象里供程序使用,如下图1,如果传进来的当前页数大于总页数,就把当前页数置为最后一页(也可以在前端页面进行设置,这里都在后端实现)如图2,还需保证当前页数不能小于1,如果小于1,则把它置为1。如图3,最后还需要计算sql语句Limit需要的参数,如图4。
执行逻辑之前可以先查看Limit语句,第一页是从第0条开始获取,取5条。第二页是从第5条开始取,取5条,以此类推。
分页查询过程:
预先定义好当前页面记录数和当前页面数,通过查询出来的记录数,可以计算出总页数,Limit的参数也就获取到了,这样Dao层就将Page对象交给XML,XML根据这两个参数查询,然后将查询的结果返回。
Mybatis如果想传入XML两个参数时,可以使用Map集合。
分页查询一般来说是按照某一字段进行排序,而且该字段值最好不要重复,可以是主键或者该字段创建的时间createTime,如果不主动设置排序方式,按照数据库默认排序方式,并不能保证每次执行sql语句的默认排序方式都是相同的。
前端的javascript的校验相当于没有校验,所以如果考虑安全的问题时,在后端也需要加上校验。
查看全部 -
接口式编程原理(中)
SqlSession接口有两个实现类,分别为DefaultSqlSession和SqlSessionManager,这里获取的对象是属于DefaultSqlSession实现类的。
首先sqlSession是我们手动封装获取的,这里调用的是Mybatis的方法,然后通过SqlSessionFactory对象的OpernSession方法获取SqlSession,其中SqlSessionFactory接口也有两个实现类,分别为DefaultSqlSessionFactory和SqlSessionManager,由源码得知SqlSessionFactoryBuilder类提供了build(Reader reader),该方法返回的是SqlSessionFactoryBuilder类提供的build(Reader reader,String envirment,Properties properties),该方法又返回的是SqlSessionFactoryBuilder提供的build(Configuration config)方法,该方法最后返回的是DefaultSqlSessionFactory对象。所以SqlSessionFactory获取的是DefaultSqlSessionFactory实现类对象。所以openSession方法是属于DefaultSqlSessionFactory里的方法。该openSession方法返回的是DefaultSqlSessionFactory提供的openSessionFromDataSource(ExecutorType execType,..),该方法最终返回的是DefaultSqlSession类的对象
所以最终获取的是DefaultSqlSession。
这里我们可以查看DefaultSqlSession的getMapper方法,该方法返回的是Configuration类提供的getMapper方法,该方法第一个参数就是传进来的class,也就是接口的类类型,第二个参数是this,也就是方法的对象,也就是sqlSession,该方法返回的是MapperRegistry类提供的getMapper方法,该方法返回值可以理解为通过代理工厂,生产一个代理返回出去(这里就是前面说的通过动态代理创建实例)。
该动态工厂的由来是Map对象(以类类型作为Key,MapperProxyFactory作为Value)的get方法,该get方法中传入一个类类型,MapperRegistry类提供了addMapper方法,该方法需要传入一个类类型,然后用Map对象的input方法把传入的类类型作为key,临时创建一个动态工厂作为value,并传入这个类类型,只有调用了这个方法才会对Map集合初始化,该方法的调用是在加载Mybatis核心配置文件时进行调用的,它会不停的调用这个方法,为不同的Class创建代理工厂。
动态工厂创建代理对象是通过MapperProxyFactory的new Instance(SqlSession sqlSession)方法,该方法中创建了代理类,该代理类的构造函数中第一个参数是传进来的sqlSession,后两个参数是代理工厂的属性,第二个参数就是接口的类类型,构造函数并没有为其他属性赋值,所以第三个参数只是Map初始化的一个对象。
查看全部 -
接口式编程原理
讲解接口式原理之前需了解如下几个问题
问题1:没有实现类的接口为甚么可以实现方法的功能(答案:是通过动态代理。简单的动态代理过程,是要有一个实现InvocationHandler接口的类,这里给这个类起名为MapperProxy,因为Mybatis源码中就是起的这个名称,这个类必须实现invoke()方法。然后用这个类通过Proxy.newProxyInstance(类加载器,接口,MapperProxy对象)创建一个代理实例,在案例中是通过sqlSession.getMapper()获取到代理实例的,表面上看是使用接口承接的,但该对象不是实现类的对象,而是一个代理实例,然后是通过这个代理实例调用接口里的方法,由动态代理知识,它并不会执行这个方法,而是会触发MapperProxy的invoke()方法,这样没有实现类的接口方法就可以执行了)。
问题2:虽然调用接口方法时,走的是invoke()方法,怎么知道调用的sql语句是什么样子的呢?(因为调用的sql语句在配置文件中,在获取SqlSession对象之前,由于调用加载Mybatis核心配置文件的方法,Mybatis配置文件中引入了sql配置文件的路径,这样加载Mybatis总配置文件的同时,也把其他配置文件加载了,这些信息Mybatis会存储到对象(Configuration)中,当代理实例调用接口方法时,如果该接口方法与配置信息能对应上(配置文件的namespace等于包名+接口名,方法名等于id名,接口参数等于parameterType,返回值类型等于resultType),就可以成功调用,根据上节课知识,这里是存在某种联系的——>接口的全名称就是配置文件的namespace,调用的方法名就是定义sql标签的id,接口的全名称和调用的方法名在invoke()方法中是可以获取到的,有了这些信息就可以获取到配置文件信息,然后再invoke方法里就可以代替原来写的方法)
问题3:根据动态代理的知识,Proxy.newProxyInstance()返回的是Object类型的对象,但却赋值给接口类型的对象?
(答案:这里是泛型的作用,当使用getMapper()传入的是什么样的类类型,就可以使用什么样的类型去接值,这就是Mybatis利用泛型进行强转了)。
查看全部 -
接口式编程(针对调用配置文件sql语句这行代码,有四处值得分析的地方)
1、namespace :方法里的namespace需要和mapper标签的namespace一致,由于两边是手写的,可能存在不一致的风险,而且也不能保证多个sql配置文件,namespace不冲突。
2、与sql关联的id :方法里的id和标签的id也是手写的,也存在不一致的风险。
3、传入的参数 :例如selectList传入的参数类型是Object,所以传入的参数不管是何种类型,它不会报错,但是如果传入的类型和parameterType类型不匹配,sql语句中引用的参数就会出错。
4、返回值:例如selectList方法,Mybatis提供了一种约束,只要是List即可,无论集合中存储何种类型,但是这样不意味执行就是对的,因为真正存入的是resultMap约束的,编译时不会报错,执行时可能就会报错。
SqlSession方法的mapper的namespace、与sql关联的id、传入的参数、返回值。
接口式编程:就是为了避免上述风险,而人为做的强制性规范和约束,Mybatis提供的这种方式,就称作接口式编程(相当于sql的配置文件有一个java接口作为代言人,这样SqlSession对象直接调用接口里的方法即可。但是该接口有一些前提:
1、namespace的统一:该接口的包名+接口名就是namespace名。
2、sql标签id的统一:代言人代言一条sql语句提供给外面,代言哪条sql语句,就提供和该sql的id相同名称方法。该接口可以代言sql配置文件的sql语句,通过提供方法,方法名为id名。
3、方法参数的统一:接口方法参数类型为parameterType类型。
4、方法返回值的统一:接口方法返回值类型为resultMap类型。
注意:如上该接口就可以代言sql语句了。该接口不用人为手动编写实现类,通过Mybatis获取该接口,就可以调用,Mybatis已经实现了该接口(它就会知道该方法是调用配置文件中的哪条sql语句)。因为当前该接口没有实现类,这里通过SqlSession对象的getMapper(接口的类类型)就可以获得该接口实现类的代理对象,这样就可以直接调用接口里的方法,而不用传入namespace+id,而是按照接口的约束进行调用)。
这样就避免了上述的问题:namespace不可能相同,因为不可能有多个相同名全路径的接口,调用方法时也不需要传入namespace。同一个接口里也不肯有多个相同名称的方法。传入的参数已经被限制,如果不符合编译会报错。返回的接口也被限定,如果不符合编译也会报错。
接口式编程的作用:
1、规范访问配置文件2、当mybatis和spring整合后,配置的数据源将交给Spring管理,也就意味着认为手写的提供SqlSession会消失,Spring将提供SqlSesssion,传入的参数应该交给Service处理好再传入进来。通过SqlSession调用接口式编程的这些代码统统都由Spring来实现。这个Dao层将会消失,该接口将会变成真正的Dao层,这时Dao层将只剩下接口文件和配置文件。
当Mybatis与Spring结合时,这些Spring提供了便捷,这里只做一些了解即可,整合之后整个Dao层只剩接口文件和配置文件。查看全部
举报