转载

今天我又去面试了,和面试官聊了半天 MyBatis

前言: 本文为《今天你面试了吗》系列文章,采用情景对话的方式还原面试场景,帮助大家梳理常用 Java 技术栈的知识点,上一篇文章发布后反响良好,此系列还会继续更新。日后我会把此系列整理成 PDF 版本,发布给大家。还请大家置顶(标星)本公众号:Java后端,第一时间接收优质博文。

昨天刚刚 面完 S pring ,根据hr的反馈说面试官对我的整体表现还算满意,然后又通知我今天有空去再聊聊有关的技术。 去的路上,我一直在想,今天会问些什么问题,JVM? 多线程? 还是分布式......真是越想心里越没底。 想着想着就到了,尽管还是那个熟悉的面试官,但那张年轻有为的面孔丝毫没有让我放下紧张的情绪。 他先开口了: 昨天的面试感觉你挺好的,你说你项目中还用的是mybatis框架作为数据库访问,那我们今天就来聊聊吧。

面试环节

面试官:你先说下你对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

面试官:那你能说下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.for each标签: 用于循环。 例如:

<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>

面试官:不错,看来你对mybatis运用的挺熟练的了。今天的面试先到这里了,回家等消息吧。

我: 好的。。

-END-

如果看到这里,说明你喜欢这篇文章,请 转发 、点赞 。同时标星(置顶)本公众号可以第一时间<今天你面试了吗>系列博文推送。

1.  《今天你面试了吗?》Spring篇

2.  ArrayList 的使用及原理,面试必问的知识点

3.   2020 年 4 月全国程序员工资出炉!

4.  面试官:MySQL千万级别大表,你要如何优化?

今天我又去面试了,和面试官聊了半天 MyBatis

原文  http://mp.weixin.qq.com/s?__biz=Mzg2MjEwMjI1Mg==&mid=2247492761&idx=1&sn=a60e11ad0b4597cb219d22f59ad999c7
正文到此结束
Loading...