zoukankan      html  css  js  c++  java
  • MyBatis理解

    采用情景对话的方式虚构面试场景,旨在帮助大家梳理常用 Java 技术栈的知识点。

    面试环节
    面试官:你先说下你对mybatis的整体理解。
    :MyBatis是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架。它避免了几乎所有JDBC代码和手动设置参数以及获取结果集。MyBatis可以对配置和原生Map使用简单的XML或注解,将接口和Java的POJO映射成数据库中的记录。
    面试官:那你们公司为什么选择Mybatis,为什么不用Hibernate呢?他两有什么区别吗?

    :mybatis的着力点在于POJO和SQL之间的映射关系,然后通过映射配置文件,将SQL所需的参数,以及返回的结果字段映射到指定POJO。Hibernate的ORM实现了POJO和数据库表之间的映射,以及SQl的自动生成和执行,也就是说Hibernate会根据制定的存储逻辑,自动生成对应的SQl并调用JDBC接口加以执行。下面我通过四个方面对比两者的区别:

    1. 开发对比:mybatis框架相对简单容易上手,针对高级查询,Mybatis需要手动编写SQL语句。Hibernate的真正掌握要比MyBatis难一些,Hibernate有良好的的映射机制,开发者无需关心SQL的生成与结果映射,可以更关注业务流程。

    2. 调优方案:mybatis可以进行详细的SQL优化设计,采用合理的session管理机制。Hibernate可以指定合理的缓存策略;尽量采用延迟加载特性;采用合理的session管理机制;采用批量抓取,设定合理的批处理参数。

    3. 扩展性方面:mybatis项目中的所有SQL语句都是依赖所用的数据库的,所以不同数据库类型的支持不好。Hibernate与具体数据库的关联只需在XML文件中配置即可,所有的HQL语句与具体使用的数据库无关,移植性很好。

    4. 缓存机制:mybatis默认情况下没开启缓存;要开启二级缓存,需要在sql映射文件中加上;映射文件中的所有select语句将会缓存,映射文件中的所有insert/update/delete会刷新缓存;缓存会使用LRU(最近最少使用)算法来回收;缓存会存储列表集合会对象的1024个引用;缓存会被视为read/write(可读可写)缓存,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。Hibernate的一级缓存是Session缓存,利用好一级缓存就需要对Session的生命周期进行管理好;二级缓存是SessionFactory级的缓存,分为内置缓存和外置缓存。

    :另外,有种说法,mybatis是半自动ORM映射工具,Hibernate是全自动的。这主要就是因为使用Hibernate查询关联对象或集合对象时,可以根据对象关系模型调用api接口直接获取。而Mybatis在查询关联对象或集合对象时,需要手动编写sql来完成,所以叫做半自动。
    我:至于我们公司为什么选择半自动的mybatis,主要是因为我们的业务经常需要编写复杂的sql,比如动态的sql。还有这种更便于我们使用索引来优化sql语句。
    面试官:你先说下JDBC的执行流程吧
    我:(1)加载JDBC驱动

         (2)建立并获取数据库连接

         (3)创建JDBC Statements对象

         (4)设置SQL语句的传入参数

         (5)执行SQL语句并获得查询结果

         (6)对查询结果进行转换处理并将处理结果返回

         (7)释放相关资源(关闭Connection,关闭Statement,关闭ResultSet)

    面试官:那你能说下mybatis执行SQL的流程吗?
    我:好的。1. 加载配置并初始化:加载配置文件,将SQl配置信息加载成为一个个MappedStatement对象(包括传入参数映射配置,执行的sql语句,结果映射的配置),存储在内存中。2. 传递调用请求:调用Mybatis提供的API,传入SQL的ID和参数对象,将请求传递给下层的请求处理层进行处理。3. 处理请求:根据SQL的ID查找到对应的MappedStatement对象;根据传入的参数对象解析MappedStatement对象,得到最终要执行的SQL和执行参数;获取数据库连接,根据得到的SQL语句和执行参数到数据库中执行,并得到执行结果;根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,得到最终的处理结果;释放连接资源;将最终的结果返回。总之,这个过程就是:加载配置->SQL解析->SQL执行->结果映射->释放连接
    面试官:很好。你刚说到初始化,你对mybatis初始化了解吗?
    我:可以这么说,Myabtis初始化的过程就是创建Configuration对象的过程。过程也很简单:

    (1)加载配置文件mybatis-config.xml到Mybatis内部。

    (2)使用Configuration对象作为一个所有配置信息的容器,这个对象的组织结构和XML配置文件的组织结构几乎完全一样,这样配置文件的信息就可以存到这个对象中,访问起来很方便。
    面试官:那我问的再深入一点,你看过mybatis的源码吗?
    我:没看过。。关键的类还是知道一点的。
    面试官:哦,那你说下你了解的mybatis的有哪些核心的类?
    我:(心想:既然面试前准备了,还是要说的,不然怎么显得自己nb一些)
    第一个是SqlSessionFactoryBuilder:通过类名就看出来这个类的主要作用是创建一个SqlSessionFactory。可以重用这个类来创建多个SqlSessionFactory实例,但是最好不要让其一直存在以保证所有的XML解析资源开放给更重要的事情。这个类可以被实例化、使用和丢弃,一旦创建了SqlSessionFactory,就不再需要它了。
    第二个是SqlSessionFactory接口:它的作用就是sql会话工厂,用于创建SqlSession。SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,它的最佳作用域是应用作用域。
    第三个是很重要的SqlSession接口:他是mybatis的一个重要接口,定义了数据库的增删改查以及事务管理的常用方法。SqlSession还提供了查找Mapper接口的有关方法。每个线程都应该有自己的SqlSession实例,因为这个实例不是线程安全的,所以它的最佳作用域是请求或方法作用域。每次收到一个HTTP请求,就可以打开一个SqlSession,返回了响应之后就关闭它。
    第四个就是我们编码的主角Mapper接口:Mapper接口是指程序员自行定义的一个数据操纵接口,类似于通常所说的DAO接口。跟DAO接口不同的地方在于Mapper接口只需要定义不需要实现,mybatis会自动为Mapper接口创建动态代理对象。Mapper接口的方法通常与Mapper配置文件中的select、insert、update和delete等XML节点存在一一对应关系。另外,附赠一张mybatis的层次结构图:

    面试官:那你能说下mybatis源码中的主要部件吗?
    我:好的,主要部件如下:

    (1)SqlSession:作为mybatis工作的主要顶层API,表示和数据库交互的会话,完成必要的数据库增删改查功能。

    (2)Executor:mybatis执行器,是Mybatis调度的核心,负责SQL语句的生成和查询缓存的维护。

    (3)StatementHandler:封装了JDBCStatement操作,负责对JDBC Statement的操作。

    (4)ParameterHandler:负责对用户传递的参数转换成JDBC Statement所需要的参数。

    (5)ResultSetHandler:负责将JDBC返回的ResultSet结果集转换成List类型的集合。

    (6)TypeHandler:负责Java数据类型和jdbc数据类型之间的映射和转换。

    (7)MappedStatement:维护了一条select/update/delete/insert节点的封装。

    (8)Sqlsource:负责根据用户传递的parameterObject,动态生成SQL语句,将信息封装在BoundSql对象中,并返回。

    (9)BoundSql:表示动态生成的SQL语句以及相应的参数信息。

    (10)Configuration:Mybatis所有的配置信息都维护在这个对象中。
    面试官:原理聊完了,接下来我们聊下实战吧。。你在项目中是怎么整合spring和mybatis的?
    我:我先说下xml的配置方式吧。
    1. 添加mybatis-spring的包:

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>x.x.x</version>
    </dependency>


    2. 配置SqlSessionFactory:整合后,可以不需要单独的mybatis配置文件,全部的配置内容可以再spring的上下文中。

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
      <property name="dataSource" ref="dataSource"/>
      <!-- 当mybatis的xml文件和mapper接口不在相同包下时,需要用mapperLocations属性指定xml文件的路径。
             *是个通配符,代表所有的文件,**代表所有目录下 -->
      <property name="mapperLocations" value="classpath:mapper/**/*.xml"/>
    <!-- 加载mybatis的全局配置文件 -->
    <property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
    </bean>

    2.1:datasource:是数据源配置,常用的有DBCP,C3P0,Druid等。

    2.2:mapperLocations:是指接口xml的文件配置,如果不配置的话映射接口类文件(mapper接口)和映射xml文件(mapper.xml)需要放在相同的包下。

    3. 配置数据映射器类:利用mybatis-spring提供的自动扫描机制:

    <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
      xsi:schemaLocation="
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">
     
    <!-- 自动扫描 -->
      <mybatis:scan base-package="org.mybatis.spring.sample.mapper" />

    </beans>

    我:(接着说)现在好像大多使用的是注解配置mybatis和数据源的方式,也就是使用java代码和spring提供的注解。(其实步骤大致差不多,由于涉及安全问题代码不透露,想学习的可以网上找。)
    面试官:你能写一个mapper映射文件中select的sql语句吗?
    我:随手写了一个,接着解释到:这个语句被称作selectPerson,接收一个int类型的参数,并返回一个HashMap类型的对象,其中的键是列名,值便是结果行中的对应值。

    <select id="selectPerson" parameterType = "int" resultType="hashmap"
    select * from person where id =#{id}
    </select>


    我(接着说):select中有这些属性可选:

    1. id:必选的,命名空间中唯一的标识符,可以被用来引用这条语句。

    2. parameterType:可选,将会传入这条语句的参数类的完全限定名或别名。

    3. resultType:从这条语句返回的期望类型的类的完全限定名或别名。如果是集合,那应该是集合包含的类型,而不是集合本身。

    4. resultMap:外部resultMap的命名引用。注意使用resultType或resultMap,不能同时使用。

    5. flushCache:默认false。设置为true表示只要语句被调用,都会导致本地缓存和二级缓存被清空。

    6. useCache:对select元素为true。设置为true会导致本条语句的结果被二级缓存。

    7. timeout:默认值为unset(依赖驱动)。这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。

    8. fetchSize:默认值为unset(依赖驱动)。这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。

    9. statementType:默认值PREPARED。这会让mybatis分别使用Statement,PreparedStatement或CallableStatement。

    面试官:当实体类中的属性名和表中的字段名不一样,应该怎么办?
    我:有两种方法。第一种比较简单粗暴:通过在sql语句中定义别名,强行让返回的字段名的的别名和实体类中的属性名一致。

    <select id="getByOrderId" parameterType="java.lang.Long" resultType="com.demo.entity.OrderInfo">
    select order_id OrderId, order_sn orderSn, total_fee totalFee, create_time createTime
    from order_info where order_id=#{orderId}
    </select>

    第二种比较优雅:通过resultMap来映射数据表的字段名和实体类的属性名之间的对应关系。(推荐)

    <resultMap id = "BaseResultMap" type="com.demo.entity.OrderInfo">
        <id property="OrderId" column="order_id"/>
        <result property="orderSn" column="order_sn"/>
        <result property="totalFee" column="total_fee"/>
        <result property="createTime" column="create_time"/>
    </resultMap>
    <select id="getByOrderId" parameterType="java.lang.Long" resultMap="BaseResultMap">
    select order_id, order_sn, total_fee, create_time
    from order_info where order_id=#{orderId}
    </select>


    面试官:如何获取自动生成的主键?

    我:一般我们插入数据的话,如果想要知道刚刚插入的数据的主键是多少,可以通过以下方式来获取。通过LAST_INSERT_ID()获取刚插入记录的自增主键值,在insert语句执行之后,执行select LAST_INSERT_ID()就可以获取自增主键。

    <insert id='insert' parameterType="com.demo.entity.OrderInfo"
        <selectKey keyProperty="orderId" order="AFTER" resultType="java.lang.Long">
            select LAST_INSERT_ID()
        </selectKey>
        insert into order_info(order_sn,total_fee,create_time)
        values(#{orderSn},#{totalFee},#{createTime)
    </insert>

    面试官:你知道mybatis的哪些动态sql?

    我:if:做条件判断的,如果不使用这个标签,肯定要在代码中做判断,比如元素是否为空,字符串是否是空字符串,还比如一些特定的枚举值需要判断执行条件。choose/when/otherwise:这个标签组合类似于if/else if.../else,就是多个选项中选择一个,如果都不满足条件,那只能执行中的内容了。例如:

    <select id="getStudentListChoose" parameterType="Student" resultMap="BaseResultMap">
        SELECT * from STUDENT WHERE 1=1
        <where>
            <choose>
                <when test="Name!=null and student!='' ">
                    AND name LIKE CONCAT(CONCAT('%', #{student}),'%')
                </when>
                <when test="hobby!= null and hobby!= '' ">
                    AND hobby = #{hobby}
                </when>
                <otherwise>
                    AND AGE = 15
                </otherwise>
            </choose>
        </where>
    </select>

    3.foreach标签:用于循环。例如:

    <select id="listByOrderIds" resultMap="BaseResultMap">
        select * from order_info where order_id in
        <foreach collection="list" item="item" open="(" close=")" separator=",">
            #{item}
        </foreach>
    </select>

    4.另外还有set标签,where标签,trim标签等。

    面试官:#{}和${}的区别是什么?
    我:#{}是解析传进来的参数,而另一个是拼接参数到SQl中。#{}是预编译处理,而另一个是字符串替换。而且#{}可以防止SQL注入。

    例如:select * from emp where name=#{empName},参数传入empName->Smith,解析执行后的SQL是:select * from emp where name=?。但是对于select * from emp where name=${empName},参数传入empName->Smith,解析执行后的SQL是:select * from emp where name='Smith'。
    面试官:在mapper中如何传递多个参数?
    我:有两种方法:1. 使用占位符的思想:(1)在映射文件中使用#{0},#{1}代表传递进来的第几个参数。(2)使用@param注解来命名参数(推荐使用) 例如:

    //mapper接口
    public OrderInfo getByOrderIdAndStatus(Long orderId, String status);

    //mapper.xml文件
    <select id="getByOrderIdAndStatus" resultMap="BaseResultMap">
        select * from order_info where order_id=#{0} and status=#{1}
    </select>


    //mapper接口
    public OrderInfo getByOrderIdAndStatus(@param("orderId")Long orderId, @param("status")String status);

    //mapper.xml文件
    <select id="getByOrderIdAndStatus" resultMap="BaseResultMap">
        select * from order_info where order_id=#{orderId} and status=#{status}
    </select>

    2.使用Map集合作为参数来装载

    Map<String, Object> map = new HashMap();
    map.put("orderId", 1L);
    map.put("status", "NORMAL");
    OrderInfo orderinfo = getByOrderIdAndStatus(map);

    //mapper接口
    public OrderInfo getByOrderIdAndStatus(Map<String, Object> map);

    //mapper.xml文件
    <select id="getByOrderIdAndStatus" parameterType="map" resultMap="BaseResultMap">
        select * from order_info where order_id=#{orderId} and status=#{status}
    </select>

  • 相关阅读:
    linux下shell显示-bash-4.1#不显示路径解决方法
    update chnroute
    An error "Host key verification failed" when you connect to other computer by OSX SSH
    使用dig查询dns解析
    DNS被污染后
    TunnelBroker for EdgeRouter 后记
    mdadm详细使用手册
    关于尼康黄的原因
    Panda3d code in github
    Python实例浅谈之三Python与C/C++相互调用
  • 原文地址:https://www.cnblogs.com/xumBlog/p/13166474.html
Copyright © 2011-2022 走看看