框架相关(8)-- Mybatis

mybatis如何从接口映射xml

mybatis作为JAVA开发最流行的ORM(object renational mapping)框架,能够将JAVA实体类和数据库中的字段结合映射起来,达到直接操作数据库的目的!

需要明确的是,所有的ORM框架都基于JAVA原生的JDBC API做了封装,所以首先我们来了解下jdbc是怎么操作数据库的?

1,注册驱动!

2,建立connection!

3,创建操作语句statement!

4,执行statement!

5,封装结果resultset!

既然是封装jdbc,也就是在jdbc的基础上进行扩展,达到使用方便的效果!

JDBC是Java提供的一个操作数据库的API;


MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装。MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java

Objects,普通的Java对象)映射成数据库中的记录。

Mybatis解决jdbc编程的问题:

1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。

解决:在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。

2、Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。

解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。

3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。

解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。

4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。

解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。

在mybatis容器初始化的时候,会自动进行驱动注册,并把xml中配置的sql语句按照命名空间(就是接口名)加sql ID的方式作为key,sql语句作为value放入hashMap中存储起来,等到使用的时候从hashmap中取出,经过反射处理得到原生的sql语句,在使用jdbc executor进行执行!执行过程中,如果有parameterType映射错误,或者SQL语句错误,则会抛出异常到应用层!得到数据操作结果以后,使用resultmap中的映射关系把数据映射到JAVA实体类中,并创建相应的实例对象!

<mapper namespace="com.huawei.paas.alsconfigservice.advanced.db.mapper.FaultRuleMapper">

    <resultMap type="com.huawei.paas.alsconfigservice.advanced.bean.FaultRuleBean"

        id="resourceMap">

        <result property="id" column="id" />

        <result property="projectId" column="projectId" />

        <result property="clusterName" column="clusterName" />

        <result property="namespace" column="namespace" />

        <result property="service" column="service" />

        <result property="appName" column="appName" />

        <result property="almSource" column="almSource" />

        <result property="aadSource" column="aadSource" />

        <result property="image" column="image" />

        <result property="logFile" column="logFile" />

        <result property="logName" column="logName" />

        <result property="eventName" column="eventName" />

        <result property="eventType" column="eventType" />

        <result property="eventFMT" column="eventFMT" />

        <result property="eventFA" column="eventFA" />

    </resultMap>

    <insert id="insertFaultRule" parameterType="list">

        INSERT INTO tbl_faultrule(projectId, clusterName, namespace, service, appName, almSource, aadSource, image, logFile, logName, eventName, eventType, eventFMT, eventFA)

        VALUES

        <foreach collection="list" item="item" index="index"

            separator=",">

            (#{item.projectId},#{item.clusterName},#{item.namespace},#{item.service},#{item.appName},#{item.almSource},#{item.aadSource},#{item.image},#{item.logFile},#{item.logName}

                ,#{item.eventName},#{item.eventType},#{item.eventFMT},#{item.eventFA})

        </foreach>

    </insert>

    <select id="queryFaultRule" parameterType="com.huawei.paas.alsconfigservice.advanced.faultdetect.FaultRuleDetailParam" resultMap="resourceMap">

        SELECT * FROM tbl_faultrule WHERE

                projectId IN

                <foreach collection="projectIds" item="projectId" open="(" separator="," close=")">

                #{projectId}

                </foreach>

                <if test="service != null and service != '' ">

                    AND service = #{service}

                </if>

                <if test="clusterName != null and clusterName != '' ">

                    AND clusterName = #{clusterName}

                </if>

                <if test="namespace != null and namespace != '' ">

                    AND namespace = #{namespace}

                </if>

                ORDER BY service asc, image asc, eventName asc

                <if test="pageIndex != -1 and pageSize != -1">

                LIMIT #{pageSize} OFFSET #{pageIndex}

                </if>

    </select>

Mybatis拦截器原理分析

Mybatis拦截器只能拦截Mybatis的四大对象,这四大对象分别是 Executor 对象,StatementHandler 对象,ParameterHandler 对象,ResultHandler对象。

Mybatis的拦截器是通过JDK动态代理实现的。在自定义的拦截器中,该拦截器实现了父类Interceptor的三个方法,其中setProperties方法获取在配置文件中为该拦截器配置的属性,如果有配置的话。这里主要讲下plugin方法和intercept方法.plugin方法是在statementHandler对象创建时被调用的,在plugin的方法实现中,一定要调用Plugin类中的wrap方法,该方法的作用是生成并返回statementHandler对象的代理类(因为这里要拦截的对象是statementHandler对象)。如果所要拦截对象方法被调用时,代理类会调用自定义拦截器的intercept方法,intercept方法可以实现你所要处理的逻辑。最后该方法调用innovation.proceed()说明调用所拦截的方法。

下面根据上边所讲加上mybatis的源码加深一下理解

1,从statementHandler对象的创建开始

Mybatis 四大对象的创建过程都是在Configuration类中实现的,并且在创建各类对象的过程中会判断配置文件是否注册了该类对象的拦截器,如果注册了该类拦截器,生成的对象是代理对象。

StatementHandler statementHandler1 = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);

 注:这条语句判断对象是否为所要拦截对象,如果是则返回代理对象,不是则直接返回,下面看下该方法的源码实现

InterceptorChain.Java

public Object pluginAll(Object target) {

    Interceptor interceptor;

    for(Iterator var2 = this.interceptors.iterator(); var2.hasNext();target =  interceptor.plugin(target)){

        interceptor =(Interceptor)var2.next();

    }

    return target;

}

注:该方法对拦截器链(在配置文件中所注册的拦截器)进行遍历,并调用拦截器中的plugin的方法,target参数为statementHandler对象。上面讲到自定义拦截器的plugin方法在拦截对象创建时被调用的。

并且plugin方法中调用Plugin类的wrap方法。下面我们来看下wrap方法的源码实现

Plugin.java

public static Object wrap(Object target, Interceptor interceptor){

    Map signatureMap =getSignatureMap(interceptor);

    Class type =target.getClass();

    Class[] interfaces =getAllInterfaces(type, signatureMap);

    return interfaces.length> 0?Proxy.newProxyInstance(type.getClassLoader(), interfaces, newPlugin(target, interceptor, signatureMap)):target;

注:这里根据@Intercepts标签所声明的信息判断statementHandler对象是否为所要拦截的目标对象,如果是,为statementHandler生成一个代理对象并返回,如果不是,则返回该对象。Mybatis是运用JDK的动态代理生成代理对象的(需理解JDK的动态代理)。当返回的是代理对象时,当调用拦截目标对象任何方法,该方法都会调用Plugin类的invoke方法,下面我们来看invoke方法的源码实现

Plugin.java

public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {

    try {

        Set e =(Set)this.signatureMap.get(method.getDeclaringClass());

        return e != null&& e.contains(method)?this.interceptor.intercept(newInvocation(this.target, method, args)):method.invoke(this.target, args);

    } catch (Exception var5){

        throw ExceptionUtil.unwrapThrowable(var5);

    }

}

注:该方法根据@Inceptors标签所声明的信息判断当前调用的方法是否是所要拦截的方法(这里是prepare方法),如果是,调用自定义拦截器的intercept方法,如果不是,则继续执行该方法(prepare方法)。

这里讲解了statementHandler对象的拦截实现,其他三类对象的拦截实现跟statementHandler对象的大同小异。希望对大家有所帮助。如有出错,望大家指正。

Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatement、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。

mybatis的核心部分分为4部分:

SqlSessionFactoryBuild(构造器):它根据配置或者代码来生成SqlSessionFactory,采用的是分步构建的Builder模式。

SqlSessionFactory(工厂接口):依靠它来生成SqlSession,使用的是工厂模式。

SqlSession(会话):既可以发送sql又可以获取Mapper接口。能提高可读性和维护性。

SqlMapper(映射器):它由一个java接口和xml文件(或注解)构成,需要给出对应的sql和映射规则,它负责发送Sql去执行并返回结果。

无论是映射器还是SqlSession都可以发送Sql到数据库执行。

使用XML构建SqlSessionFactory

在Mybatis中分为两类:一类是基础配置文件,主要配置一些最基本的上下文参数和运行环境,另一类是映射文件,它是配置映射关系、SQL、参数信息等信息。

InputStream inputStream =Resources.getResourceAsStream("mybatis-config.xml");

SqlSessionFactory = newSqlSessionFactoryBuilder().build(inputStream);

mybatis的基础配置文件命名为mybatis-config.xml

首先读取mybatis-config.xml,然后通过SqlsessionFactoryBuilder的Builder方法去创建SqlSessionFactory。


SqlSession(会话)

SqlSession是核心接口,它的作用相当于JDBC的connection对象,代表着一个连接资源的启用。具体作用有三个:

1.获取Mapper接口

2.发送Sql给数据库

3.控制数据库事务

有了SqlSessionFactory,创建SqlSession就非常简单了。

SqlSession sqlSession=SqlSessionFactory.openSession();


SqlMapper(映射器)

映射器是Mybatis最重要、最复杂的组件,它是由一个对应的接口和XML文件(或注解)组成。

它可以配置如下内容:

1.描述映射的规则

2.提供SQL语句,并可以配置SQL参数类型,返回类型,缓存刷新等信息。

3.配置缓存

4.提供动态SQL


首先先定义一个POJO类

package com.learn.ssm.pojo;

public class Role {

    private Long id;

    private String roleName;

    private String note;

    /* getter and setter */

}   

映射器的主要作用就是将SQL查询到的结果映射为一个POJO,或者将POJO的数据插入到数据库中,并定义一些关于缓存等的重要内容。注意:开发的只是一个接口而不是一个实现类。接口不能直接运行,Mybatis运用了动态代理技术让接口能运行起来。Mybatis为这个接口生成一个代理对象,代理对象去处理相关的逻辑。

用XML实现映射器

用Xml定义映射器分为两个部分:接口和XML。

首先定义一个映射器接口:

package com.learn.ssm.mapper;

public interface RoleMapper {

    public Role getRole(Long id);

}

在用XML方式创建SqlSession的配置文件中有这样一段代码:

它就是引用一个XML文件,用XML方式创建映射器

parameterType和resultType

parameterType:指定输入参数类型,mybatis通过ognl从输入对象中获取参数值拼接在sql中。

resultType:指定输出结果类型,mybatis将sql查询结果的一行记录数据映射为resultType指定类型的对象。

selectOne和selectList

selectOne查询一条记录

selectList可以查询一条或多条记录

mysql自增主键返回

通过修改sql映射文件,可以将mysql自增主键返回:

<insert id="insertUser" parameterType="cn.swun.po.User">

    <!-- selectKey将主键返回,需要再返回 -->

    <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">

        select LAST_INSERT_ID()

    </selectKey>

      insert into user(username,birthday,sex,address)

        values(#{username},#{birthday},#{sex},#{address});

</insert>

添加selectKey实现将主键返回

keyProperty: 返回的主键存储在pojo中的哪个属性

order:selectKey的执行顺序,是相对与insert语句来说,由于mysql的自增原理执行完insert语句之后才将主键生成,所以这里selectKey的执行顺序为after

resultType:返回的主键是什么类型


用Mapper接口发送SQL

SqlSession还可以获取Mapper接口,通过Mapper接口发送SQL:

RoleMapper roleMapper=sqlSession.getMapper(RoleMapper.class); //通过sqlSession方法获取Mapper接口,就可以调用它的方法了。

Role role=roleMapper.getRole(1L)

因为XML文件或接口注解定义的SQL都可以通过"类的全限定名+方法名"查找,所以mybatis会启用对应的sql进行运行,并返回结果。


mybatis的常用功能:

1,使用xml文件配置使用映射

2,使用typeAliases修改类型别名

3,使用插件进行方法拦截

4,使用类型句柄(typehandlers)匹配java的参数或者返回值类型

5,使用环境(environments)配置多个不同的环境,以便使用不同的数据库

6,使用事务管理器(Transaction)管理事务

7,使用动态SQL

8,处理一对一关系使用联合(association),处理一对多使用聚集(cellection)

9,使用识别器(discriminator)对产生的结果集进行筛选(类似switch语句)

10,使用cache开启缓存

11,使用缓存引用res-cache(让不同命名空间都能使用同一个缓存机制)

#{}和${}

1.#{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。

如果传入的是基本类型,那么#{}中的变量名称可以随意写

如果传入的参数是pojo类型,那么#{}中的变量名称必须是pojo中的属性

2.${}表示拼接sql串,通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。

如果传入的是基本类型,那么${}中的变量名必须是value

如果传入的参数是pojo类型,那么${}中的变量名称必须是pojo中的属性

注意:使用拼接符有可能造成sql注入,在页面输入的时候可以加入校验,不可输入sql关键字,不可输入空格

推荐阅读更多精彩内容