💂 个人主页: Java程序鱼

💬 如果文章对你有帮助,欢迎关注、点赞、收藏(一键三连)和订阅专栏

👤 微信号:hzy1014211086,想加入技术交流群的小伙伴可以加我好友,群里会分享学习资料、学习方法


序号内容链接地址
1Java基础知识面试题https://blog.csdn.net/qq_35620342/article/details/119636436
2Java集合容器面试题https://blog.csdn.net/qq_35620342/article/details/119947254
3Java并发编程面试题https://blog.csdn.net/qq_35620342/article/details/119977224
4Java异常面试题https://blog.csdn.net/qq_35620342/article/details/119977051
5JVM面试题https://blog.csdn.net/qq_35620342/article/details/119948989
6Java Web面试题https://blog.csdn.net/qq_35620342/article/details/119642114
7Spring面试题https://blog.csdn.net/qq_35620342/article/details/119956512
8Spring MVC面试题https://blog.csdn.net/qq_35620342/article/details/119965560
9Spring Boot面试题https://blog.csdn.net/qq_35620342/article/details/120333717
10MyBatis面试题https://blog.csdn.net/qq_35620342/article/details/119956541
11Spring Cloud面试题待分享
12Redis面试题https://blog.csdn.net/qq_35620342/article/details/119575020
13MySQL数据库面试题https://blog.csdn.net/qq_35620342/article/details/119930887
14RabbitMQ面试题待分享
15Dubbo面试题待分享
16Linux面试题待分享
17Tomcat面试题待分享
18ZooKeeper面试题待分享
19Netty面试题待分享
20数据结构与算法面试题待分享

文章目录


1.MyBatis是什么?

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

2.ORM是什么

ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简单Java对象(POJO)的映射关系的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中。

3.为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?

Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。

而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。

4.传统JDBC开发存在的问题

JDBC是Java连接关系数据库的底层API,利用JDBC开发持久层存在诸多问题,而MyBatis正是为了解决JDBC的以下问题而生的。

  • 数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
  • Sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
  • 使用PreparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
  • 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。

5.JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?

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

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

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

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

3、使用PreparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。

解决: Mybatis自动将java对象映射至sql语句。

4、对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。

解决:Mybatis自动将sql执行结果映射至java对象。

6.MyBatis与Hibernate

MyBatis和Hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过Mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。

MyBatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。

Hibernate对象/关系映射能力强,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用Hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。

7.Mybatis优缺点

优点

与传统的数据库访问技术相比,ORM有以下优点:

  • 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用
  • 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接
  • 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)
  • 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护
  • 能够与Spring很好的集成

缺点

  • SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求
  • SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库

8.MyBatis框架适用场景

  • MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
  • 对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。

9.MyBatis编程步骤是什么样的?

(1)获取SqlSessionFactory对象
解析文件的每一个信息保存在Configuration中,返回包含Configuration的DefaultSqlSession;
注意:【MappedStatement】:代表一个增删改查的详细信息

(2)获取SqlSession对象
返回一个DefaultSqlSession对象,包含Executor和Configuration;
这一步会创建Executor;

(3)获取接口的代理对象(MapperProxy)
getMapper,使用MapperProxyFactory创建一个MapperProxy的代理对象,代理对象里包含了DefaultSqlSession(Executor)

(4)执行增删改查方法

(5)关闭会话

10.请说说MyBatis的工作原理

在学习 MyBatis 程序之前,需要了解一下 MyBatis 工作原理,以便于理解程序。MyBatis 的工作原理如下图

MyBatis工作原理

  • SqlMapConfig.xml,此文件作为MyBatis的全局配置文件,配置了mybatis的运行环境等信息。Mapper.xml文件即SQL映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。
  • 通过MyBatis环境等配置信息构造SqlSessionFactory即会话工厂
  • 由会话工厂创建SqlSession即会话,操作数据库需要通过SqlSession进行。
  • Mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。
  • Mapped Statement也是MyBatis一个底层封装对象,它包装了MyBatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。
  • Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对PreparedStatement设置参数。
  • Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、Pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至Java对象中,输出结果映射过程相当于Jdbc编程中对结果的解析处理过程。

11.MyBatis功能架构

Mybatis功能框架
我们把Mybatis的功能架构分为三层:

  • API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
  • 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
  • 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

12.MyBatis的框架架构设计是怎么样的

Mybatis框架架构

这张图从上往下看。MyBatis的初始化,会从mybatis-config.xml配置文件,解析构造成Configuration这个类,就是图中的红框。

(1)加载配置:配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。

(2)SQL解析:当API接口层接收到调用请求时,会接收到传入SQL的ID和传入对象(可以是Map、JavaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。

(3)SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。

(4)结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。

13.为什么需要预编译

1.定义:
SQL 预编译指的是数据库驱动在发送 SQL 语句和参数给 DBMS 之前对 SQL 语句进行编译,这样 DBMS 执行 SQL 时,就不需要重新编译。

2.为什么需要预编译
JDBC 中使用对象 PreparedStatement 来抽象预编译语句,使用预编译。预编译阶段可以优化 SQL 的执行。预编译之后的 SQL 多数情况下可以直接执行,DBMS 不需要再次编译,越复杂的SQL,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。同时预编译语句对象可以重复利用。把一个 SQL 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个SQL,可以直接使用这个缓存的 PreparedState 对象。Mybatis默认情况下,将对所有的 SQL 进行预编译。

14.Mybatis都有哪些Executor执行器?它们之间的区别是什么?

  • SimpleExecutor是最简单的执行器,根据对应的sql直接执行即可,不会做一些额外的操作;
  • BatchExecutor执行器,顾名思义,通过批量操作来优化性能。通常需要注意的是批量更新操作,由于内部有缓存的实现,使用完成后记得调用flushStatements来清除缓存。
  • ReuseExecutor 可重用的执行器,重用的对象是Statement,也就是说该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能。内部的实现是通过一个HashMap来维护Statement对象的。由于当前Map只在该session中有效,所以使用完成后记得调用flushStatements来清除Map。

15.Mybatis中如何指定使用哪一种Executor执行器?

在Mybatis配置文件中,在设置(settings)可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数,如SqlSession openSession(ExecutorType execType)。

配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。

16.Mybatis延迟加载

在实际使用中,我们会经常性的涉及到多表联合查询,但是有时候,并不会立即用到所有的查询结果,我来举两个例子:

  • 例如,查询一批笔记本电脑的进货明细,而不直接展示每列明细对应电脑配置或者价格等的详细信息,等到用户需要取出某笔记本相关的详细信息的时候,再进行单表查询
  • 再例如 ,银行中,某个用户拥有50个账户(打比方),再我们查询这个而用户的信息,这个用户下所有账户的详细信息很显然,在使用的时候再查询才是比较合理的

针对这样一种情况,延迟加载这一种机制就出现了,延迟加载(懒加载)顾名思义,就是对某种信息推迟加载,这样的技术也就帮助我们实现了 “按需查询” 的机制,在一对多,或者多对多的情况下

既然提到了延迟加载,当然顺便提一句立即加载,它的含义就是不管是否用户需要,一调用,则马上查询,这种方式,适合与多对一,或者一对一的情况下。

首先,配置基本的环境,然后我们首先在数据库准备两张表

User表

CREATE TABLE USER (
 `id`            INT(11)NOT NULL AUTO_INCREMENT,
 `username`     VARCHAR(32) NOT NULL COMMENT '用户名',
 `telephone`    VARCHAR(11) NOT NULL COMMENT '手机',
 `birthday`        DATETIME DEFAULT NULL COMMENT '生日',
 `gender`          CHAR(1) DEFAULT NULL COMMENT '性别',
 `address`         VARCHAR(256) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY  (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

Account表

CREATE TABLE `account` (
  `ID` int(11) NOT NULL COMMENT '编号',
  `UID` int(11) default NULL COMMENT '用户编号',
  `MONEY` double default NULL COMMENT '金额',
  PRIMARY KEY  (`ID`),
  KEY `FK_Reference_8` (`UID`),
  CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

然后分别创建出其对应的实体类

User类

public class User implements Serializable {
    private Integer id;
    private String username;
    private String telephone;
    private Date birthday;
    private String gender;
    private String address;
    //一对多关系映射,主表实体应该包含从表实体的集合引用
    private List<Account> accounts;
    ...... 请补充 get set 和 toString 方法
}

Account类

public class Account implements Serializable {
    private Integer id;
    private Integer uid;
    private Double money;
    //从表实体应该包含一个主表实体的对象引用
    private User user;
    ...... 请补充 get set 和 toString 方法
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.ideal.mapper.UserMapper">

    <!-- 定义User的resultMap-->
    <resultMap id="userAccountMap" type="User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="telephone" column="telephone"></result>
        <result property="birthday" column="birthday"></result>
        <result property="gender" column="gender"></result>
        <result property="address" column="address"></result>
        <collection property="accounts" ofType="account">
            <id property="id" column="aid"></id>
            <result property="uid" column="uid"></result>
            <result property="money" column="money"></result>
        </collection>
    </resultMap>
    
    <!-- 查询所有用户 并且显示对应账户信息 -->
    <select id="findAll" resultMap="userAccountMap">
       SELECT u.*,a.id as aid,a.uid,a.money FROM user u LEFT OUTER JOIN account a on u.id = a.uid;
    </select>

    <!-- 根据id查询用户 -->
    <select id="findById" parameterType="INT" resultType="User">
        select * from user where id = #{uid}
    </select>
    
</mapper>

两个接口中创建对应方法

public interface AccountMapper {
    /**
     * 查询所有账户
     * @return
     */
    List<Account> findAll();
}
public interface UserMapper {
    /**
     * 查询所有用户信息,同时显示出该用户下的所有账户
     *
     * @return
     */
    List<User> findAll();

    /**
     * 根据id查询用户信息
     * @param userId
     * @return
     */
    User findById(Integer userId);
}

延迟加载代码实现

首先,给大家演示一下,我们之前一对一查询用户的方式,同时会将用户对应所有的账户信息,也查询出来

/**
* 测试查询所有
*/
@Test
public void testFindAll() {
    List<User> users= userMapper.findAll();
    for (User user : users) {
        System.out.println("---------------------");
        System.out.println(user);
        System.out.println(user.getAccounts());
    }
}

效果:
在这里插入图片描述

这种方式是通过 SQL 语句,以及resultMap将 用户和账户的信息同时查询出来

那么如何实现我们上面所说的延迟加载呢?

这次我们选择 查询账户,然后延迟加载用户的信息

修改AccountMapper.xml
首先需要修改的就是账户的映射配置文件,可以看到我们在查询时,依旧定义了一个 resultMap 先封装了 Account ,然后通过association 进行关联 User,其中使用的就是 select 和 column 实现了延迟加载用户信息

  • select 用来指定延迟加载所需要执行的 SQL 语句,也就是指定 某个SQL映射文件中的某个select标签对的 id,在这里我们指定了用户中通过id查询信息的方法
  • column 是指关联的用户信息查询的列,在这里也就是关联的用户的主键即,id
<mapper namespace="cn.ideal.mapper.AccountMapper">
    <!-- 定义封装 Account和User 的resultMap -->
    <resultMap id="userAccountMap" type="Account">
        <id property="id" column="id"></id>
        <result property="uid" column="uid"></result>
        <result property="money" column="money"></result>
        <!-- 配置封装 User 的内容
            select:查询用户的唯一标识
            column:用户根据id查询的时候,需要的参数值
        -->
        <association property="user" column="uid" javaType="User" select="cn.ideal.mapper.UserMapper.findById"></association>
    </resultMap>

    <!-- 根据查询所有账户 -->
    <select id="findAll" resultMap="userAccountMap">
        SELECT * FROM account
    </select>
</mapper>

第一次测试代码
我们只执行一下账户的查询所有方法,看一下,是否能够实现我们的效果

@Test
public void testFindAll(){
    List<Account> accounts = accountMapper.findAll();
}

执行效果
在这里插入图片描述
可以看到,三条 SQL 语句都执行了,这是为什么呢?

这是因为,我们在测试方法之前,需要开启延迟加载功能

延迟加载功能

经过查阅文档,我们知道了,如果想要开始延迟加载功能,就需要在总配置文件 SqlMapConfig.xml 中配置 setting 属性,也就是将延迟加载 lazyLoadingEnable 的开关设置成 teue ,由于是按需加载,所以还需要将积极加载修改为消极加载,也就是将 aggressiveLazyLoading 改为 false

当然,由于我这里导入的 MyBatis 版本为 3.4.5 所以这个值默认就是 false 实际上不用设置也可以,不过我们还是写出来

<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
     <setting name="aggressiveLazyLoading" value="false"></setting>
</settings>

注意:如果有使用typeAliases配置别名的话一定要将 typeAliases 标签放在后面

再次测试

仍然只执行查询方法

@Test
public void testFindAll(){
    List<Account> accounts = accountMapper.findAll();
}

执行效果
在这里插入图片描述

这一次果然只执行了一条查询 account 的命令

那么当用户想要查看到,每个账户对应下的用户的时候呢?这也就是按需查询,只需要在测试时,加入对应获取方法就可以了

@Test
public void testFindAll(){
    List<Account> accounts = accountMapper.findAll();
    for (Account account : accounts){
        System.out.println("----------------------------");
        System.out.println(account);
        System.out.println(account.getUser());
    }
}

执行一下
在这里插入图片描述
可以看到,我们延迟加载的目的达到了

17.#{}和${}的区别

#{} 是预编译处理,像传进来的数据会加个" "(#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号)

就 是 字 符 串 替 换 。 直 接 替 换 掉 占 位 符 。 {} 就是字符串替换。直接替换掉占位符。 方式一般用于传入数据库对象,例如传入表名.

使用 ${} 的话会导致 sql 注入。什么是 SQL 注入呢?比如 select * from user where id = ${value}

value 应该是一个数值吧。然后如果对方传过来的是 001 and name = tom。这样不就相当于多加了一个条件嘛?把SQL语句直接写进来了。如果是攻击性的语句呢?001;drop table user,直接把表给删了

所以为了防止 SQL 注入,能用 #{} 的不要去用 ${}

如果非要用 ${} 的话,那要注意防止 SQL 注入问题,可以手动判定传入的变量,进行过滤,一般 SQL 注入会输入很长的一条 SQL 语句

18.模糊查询like语句该怎么写

方式1:$ 这种方式,简单,但是无法防止SQL注入,所以不推荐使用

LIKE  '%${name}%'

方式2:#

LIKE "%"#{name}"%"

方式3:字符串拼接

AND name LIKE CONCAT(CONCAT('%',#{name},'%'))

方式4:bind标签

<select id="searchStudents" resultType="com.example.entity.StudentEntity"
  parameterType="com.example.entity.StudentEntity">
  <bind name="pattern1" value="'%' + _parameter.name + '%'" />
  <bind name="pattern2" value="'%' + _parameter.address + '%'" />
  SELECT * FROM test_student
  <where>
   <if test="age != null and age != '' and compare != null and compare != ''">
    age
    ${compare}
    #{age}
   </if>
   <if test="name != null and name != ''">
    AND name LIKE #{pattern1}
   </if>
   <if test="address != null and address != ''">
    AND address LIKE #{pattern2}
   </if>
  </where>
  ORDER BY id
 </select>

方式5:java代码里写
param.setUsername("%CD%"); 在 java 代码中传参的时候直接写上

<if test="username!=null"> AND username LIKE #{username}</if>

然后 mapper 里面直接写 #{} 就可以了

19.在mapper中如何传递多个参数

方法一:使用map接口传递参数
严格来说,map适用几乎所有场景,但是我们用得不多。原因有两个:首先,map是一个键值对应的集合,使用者要通过阅读它的键,才能明了其作用;其次,使用map不能限定其传递的数据类型,因此业务性质不强,可读性差,使用者要读懂代码才能知道需要传递什么参数给它,所以不推荐用这种方式传递多个参数。

public List<Role> findRolesByMap(Map<String, Object> parameterMap);
<select id="findRolesByMap" parameterType="map" resultType="role">
    select id, role_name as roleName, note from t_role where role_name like concat('%', #{roleName}, '%') and note like concat('%', #{note}, '%')
</select>

方法二:使用注解传递多个参数  
MyBatis为开发者提供了一个注解@Param(org.apache.ibatis.annotations.Param),可以通过它去定义映射器的参数名称,使用它可以得到更好的可读性  这个时候需要修改映射文件的代码,此时并不需要给出parameterType属性,让MyBatis自动探索便可以了  使可读性大大提高,使用者也方便了,但是这会带来一个麻烦。如果SQL很复杂,拥有大于10个参数,那么接口方法的参数个数就多了,使用起来就很不容易,不过不必担心,MyBatis还提供传递Java Bean的形式。

public List<Role> findRolesByAnnotation(@Param("roleName") String rolename, @Param("note") String note);
<select id="findRolesByAnnotation" resultType="role">
    select id, role_name as roleName, note from t_role where role_name like concat('%', #{roleName}, '%') and note like concat('%', #{note}, '%')
</select>

方法三:通过Java Bean传递多个参数

public List<Role> findRolesByBean(RoleParams roleParam);
<select id="findRolesByBean" parameterType="com.xc.pojo.RoleParams" resultType="role">
    select id, role_name as roleName, note from t_role where role_name like concat('%', #{roleName}, '%') and note like concat('%', #{note}, '%')
</select>

方法四:混合使用  
在某些情况下可能需要混合使用几种方法来传递参数。举个例子,查询一个角色,可以通过角色名称和备注进行查询,与此同时还需要支持分页

public List<Role> findByMix(@Param("params") RoleParams roleParams, @Param("page") PageParam PageParam);
<select id="findByMix" resultType="role">
    select id, role_name as roleName, note from t_role
    where role_name like concat('%', #{params.roleName}, '%') and note like concat('%', #{params.note}, '%') limit #{page.start}, #{page.limit}
</select>

20.Mybatis如何执行批量操作

Mybatis常会出现批量操作,如批量查询,批量插入,批量修改(replace into)。批量操作要比循环执行效率提升很多,这里对mybatis的批量操作做一个总结讲解。

(1)Foreach
foreach:foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。foreach元素的属性主要有 item,index,collection,open,separator,close。它是批量操作的核心标签,下面都是foreach在不同场景的应用和写法。

List<String>

select id, accid, key1, key1createtime key1CreateTime, key2, key2createtime key2CreateTime
        from m_acc_keys where accid in
<foreach item="item" index="index" collection="paramIds" open="(" separator="," close=")">
       #{item}
</foreach>

注意:你可以传递一个 List 实例或者数组作为参数对象传给 MyBatis。当你这么做的时候,MyBatis 会自动将它包装在一个 Map 中,用名称在作为键。List 实例将会以“list”作为键,而数组实例将会以“array”作为键。

List<Object>
这是一个批量保存(插入或修改)的例子

<insert id="insertList" parameterType="list" >
    replace into xx (id, c1,c2) values
    <foreach collection="list" item="it" separator=",">
        (#{it.id},#{it.c1},#{it.c2})
    </foreach>
</insert>

replace:会根据主键和唯一索引判断该记录是否存在,存在就删除在插入(修改),不存在就插入(insert)。

(2)数组
还是一个查询的例子

public List<Column> selectByIds(String[] ids); 
<select id="selectByIds" parameterType="java.lang.String" 
	resultType="columnMap">
	<include refid="select_clause"></include>
	where c.id in 
	<foreach collection="array" item="id" open="(" separator="," close=")">
           #{id}
       </foreach>
</select>

(3)多重循环
colids和proids是两个平级的数据。

<delete id="move" parameterType="java.util.Map">
	insert into b_column_programme(columnId, programmeId) 
	values 
      	<foreach collection="colids" item="colid" separator="," >
           <foreach collection="proids" item="proid" separator="," >
            (#{colid}, #{proid})
        </foreach>
       </foreach>
</delete>

(4)批量插入id自增长
Mybatis在版本3.4.x以上支持批量插入绑定自增长id,常用版本3.4.1。

对应的maven支持

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

Mapper.xml写法

<insert id="insertList" parameterType="list" useGeneratedKeys="true" keyProperty="id" >
    insert into xx
    (c1,c2)
    values
    <foreach collection="list" item="it" separator=",">
        (#{it.c1},#{it.c2})
    </foreach>
</insert>

21.如何获取生成的主键

MySQL:Mapper 文件 insert 语句设置

useGeneratedKeys="true" keyProperty="id"

Oracle:Mapper 文件 insert 语句增加

<selectKey keyProperty="id" order="BEFORE" resultType="Integer">
    select xxx_SEQ.nextval from dual
</selectKey>

22.当实体类中的属性名和表中的字段名不一样 ,怎么办

第1种: 通过在查询的SQL语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。

<select id="getOrder" parameterType="int" resultType="com.jourwon.pojo.Order">
       select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
</select>

第2种: 通过<resultMap>来映射字段名和实体类属性名的一一对应的关系。

<select id="getOrder" parameterType="int" resultMap="orderResultMap">
	select * from orders where order_id=#{id}
</select>

<resultMap type="com.jourwon.pojo.Order" id="orderResultMap">
    <!–用id属性来映射主键字段–>
    <id property="id" column="order_id">

    <!–用result属性来映射非主键字段,property为实体类属性名,column为数据库表中的属性–>
    <result property ="orderno" column ="order_no"/>
    <result property="price" column="order_price" />
</reslutMap>

23.Mapper 编写有哪几种方式?

第一种:接口实现类继承 SqlSessionDaoSupport:使用此种方法需要编写mapper 接口,mapper 接口实现类、mapper.xml 文件。

(1)在 sqlMapConfig.xml 中配置 mapper.xml 的位置

<mappers>
    <mapper resource="mapper.xml 文件的地址" />
    <mapper resource="mapper.xml 文件的地址" />
</mappers>

(2)定义 mapper 接口

(3)实现类集成 SqlSessionDaoSupport

mapper 方法中可以 this.getSqlSession()进行数据增删改查。

(4)spring 配置

<bean id=" " class="mapper 接口的实现">
    <property name="sqlSessionFactory"
    ref="sqlSessionFactory"></property>
</bean>

第二种:使用 org.mybatis.spring.mapper.MapperFactoryBean:

(1)在 sqlMapConfig.xml 中配置 mapper.xml 的位置,如果 mapper.xml 和mappre 接口的名称相同且在同一个目录,这里可以不用配置

<mappers>
    <mapper resource="mapper.xml 文件的地址" />
    <mapper resource="mapper.xml 文件的地址" />
</mappers>

(2)定义 mapper 接口:

(3)mapper.xml 中的 namespace 为 mapper 接口的地址

(4)mapper 接口中的方法名和 mapper.xml 中的定义的 statement 的 id 保持一致

(5)Spring 中定义

<bean id="" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="mapper 接口地址" />
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

第三种:使用 mapper 扫描器:

(1)mapper.xml 文件编写:

mapper.xml 中的 namespace 为 mapper 接口的地址;

mapper 接口中的方法名和 mapper.xml 中的定义的 statement 的 id 保持一致;

如果将 mapper.xml 和 mapper 接口的名称保持一致则不用在 sqlMapConfig.xml中进行配置。

(2)定义 mapper 接口:

注意 mapper.xml 的文件名和 mapper 的接口名称保持一致,且放在同一个目录

(3)配置 mapper 扫描器:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="mapper 接口包地址
    "></property>
    <property name="sqlSessionFactoryBeanName"
    value="sqlSessionFactory"/>
</bean>

(4)使用扫描器后从 spring 容器中获取 mapper 的实现对象。

24.MyBatis接口绑定的几种方式

接口绑定有两种方式

(1)使用注解,在接口的方法上面添加@Select@Update等注解,里面写上对应的SQL语句进行SQL语句的绑定。

(2)通过映射文件xml方式进行绑定,指定xml映射文件中的namespace对应的接口的全路径名

什么时候用注解绑定?什么时候用xml绑定?

当SQL语句比较简单的时候,使用注解绑定就可以了,当SQL语句比较复杂的话,使用xml方式绑定,一般用xml方式绑定比较多

25.使用MyBatis的mapper接口调用时有哪些要求?

  • Mapper接口方法名和mapper.xml中定义的每个sql的id相同。

  • Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同。

  • Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同。

  • Mapper.xml文件中的namespace即是mapper接口的类路径。

26.通常一个mapper.XML对应一个DAO接口,DAO是否可以重载?

答:不能重载,方法名对应的 mapper.xml 文件里的一个 id,这个与方法名对应,系统会根据 namespace+id 找到对应的方法对应。

Dao 接口即 Mapper 接口。接口的全限名,就是映射文件中的 namespace 的值;接口的方法名,就是映射文件中 Mapper 的 Statement 的 id 值;接口方法内的参数,就是传递给 sql 的参数。Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个 MapperStatement。在 Mybatis 中,每一个标签,都会被解析为一个MapperStatement 对象。

举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到 namespace 为 com.mybatis3.mappers.StudentDao 下面 id 为findStudentById 的 MapperStatement。

Mapper 接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK动态代理为 Mapper 接口生成代理对象 proxy,代理对象会拦截接口方法,转而执行 MapperStatement 所代表的 sql,然后将 sql 执行结果返回。

27.MyBatis不同映射文件中的id是否可以重复?

可以重复,但是需要映射文件的namespace不同

不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置 namespace,那么 id 不能重复。

原因就是 namespace+id 是作为 Map<String, MapperStatement>的 key使用的,如果没有 namespace,就剩下 id,那么,id 重复会导致数据互相覆盖。

有了 namespace,自然 id 就可以重复,namespace 不同,namespace+id 自然也就不同。

28.简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系?

答:Mybatis将所有Xml配置信息都封装到All-In-One重量级对象Configuration内部。在Xml映射文件中,<parameterMap>标签会被解析为ParameterMap对象,其每个子元素会被解析为ParameterMapping对象。<resultMap>标签会被解析为ResultMap对象,其每个子元素会被解析为ResultMapping对象。每一个<select><insert><update><delete>标签均会被解析为MappedStatement对象,标签内的sql会被解析为BoundSql对象。

29.Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?

第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系。

第二种是使用sql列的别名功能,将列别名书写为对象属性名,比如T_NAME AS NAME,对象属性名一般是name,小写,但是列名不区分大小写,Mybatis会忽略列名大小写,智能找到与之对应对象属性名,你甚至可以写成T_NAME AS NaMe,Mybatis一样可以正常工作。

有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。

30.Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?

还有很多其他的标签,<resultMap><parameterMap><sql><include><selectKey>,加上动态sql的9个标签,trim|where|set|foreach|if|choose|when|otherwise|bind等,其中<sql>为sql片段标签,通过<include>标签引入sql片段,<selectKey>为不支持自增的主键生成策略标签。

31.MyBatis映射文件中A标签引用B标签,如果B标签在A的后面定义,可以吗?

虽然 Mybatis 解析 Xml 映射文件是按照顺序解析的,但是,被引用的 B 标签依然可以定义在任何地方,Mybatis 都可以正确识别。

原理:
Mybatis 解析 A 标签时,发现引用了 B 标签,未解析到 B 标签,此时会把 A 标签标记为未解析状态;

继续解析下面内容,把剩下解析完之后,再解析标记为未解析的标签;

此时已解析到 B 标签,此时再解析A标签时,B标签已经存在,A 标签也就顺利解析完成。

32.MyBatis实现一对一,一对多有几种方式,怎么操作的?

有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面的association,collection节点配置一对一,一对多的类就可以完成

嵌套查询是先查一个表,根据这个表里面的结果的外键id,去再另外一个表里面查询数据,也是通过配置association,collection,但另外一个表的查询通过select节点配置。

33.Mybatis是否可以映射Enum枚举类?

Mybatis可以映射枚举类,不单可以映射枚举类,Mybatis可以映射任何对象到表的一列上。映射方式为自定义一个TypeHandler,实现TypeHandler的setParameter()和getResult()接口方法。

TypeHandler有两个作用,一是完成从javaType至jdbcType的转换,二是完成jdbcType至javaType的转换,体现为setParameter()和getResult()两个方法,分别代表设置sql问号占位符参数和获取列查询结果。

34.Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?

Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能,Mybatis提供了9种动态sql标签trim|where|set|foreach|if|choose|when|otherwise|bind。

其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此来完成动态sql的功能。

35.Mybatis是如何进行分页的?

(1)原始方法,使用 limit,需要自己处理分页逻辑:

对于 mysql 数据库可以使用 limit ,如:

select * from table limit 5; --返回前5行

select * from table limit 0,5; --同上,返回前5行

select * from table limit 5,10; --返回6-15行

对于 oracle 数据库可以使用 rownum ,如:从表Sys_option(主键为sys_id)中从第10条记录开始检索20条记录,语句如下:

SELECT * FROM (SELECT ROWNUM R,t1.* From Sys_option where rownum < 30 ) t2

Where t2.R >= 10

(2)拦截StatementHandler,其实质还是在最后生成limit语句

(3)使用PageHelper插件,这是目前比较常见的方法

36.简述Mybatis的插件运行原理,以及如何编写一个插件。

Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。

实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。

37.Mybatis缓存机制

缓存机制减轻数据库压力,提高数据库性能

mybatis的缓存分为两级:一级缓存、二级缓存

(1)一级缓存:
一级缓存为 ​sqlsesson​ 缓存,缓存的数据只在 SqlSession 内有效。在操作数据库的时候需要先创建 SqlSession 会话对象,在对象中有一个 HashMap 用于存储缓存数据,此 HashMap 是当前会话对象私有的,别的 SqlSession 会话对象无法访问。

具体流程:

第一次执行 select 完毕会将查到的数据写入 SqlSession 内的 HashMap 中缓存起来

第二次执行 select 会从缓存中查数据,如果 select 同传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率

注意:

1、如果 SqlSession 执行了 DML 操作(insert、update、delete),并 commit 了,那么 mybatis 就会清空当前 SqlSession 缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现差异

2、当一个 SqlSession 结束后那么他里面的一级缓存也就不存在了, mybatis 默认是开启一级缓存,不需要配置

3、 mybatis 的缓存是基于 [namespace:sql语句:参数] 来进行缓存的,意思就是, SqlSession 的 HashMap 存储缓存数据时,是使用 [namespace:sql:参数] 作为 key ,查询返回的语句作为 value 保存的

(2)二级缓存:
二级缓存是​ mapper​ 级别的缓存,也就是同一个 namespace 的 mapper.xml ,当多个 SqlSession 使用同一个 Mapper 操作数据库的时候,得到的数据会缓存在同一个二级缓存区域

二级缓存默认是没有开启的。需要在 setting 全局参数中配置开启二级缓存

开启二级缓存步骤:

1、​conf.xml​ 配置全局变量开启二级缓存

<settings>
    <setting name="cacheEnabled" value="true"/>默认是false:关闭二级缓存
<settings>

2、在​ userMapper.xml ​中配置

当前mapper下所有语句开启二级缓存
这里配置了一个 LRU 缓存,并每隔60秒刷新,最大存储512个对象,而返回的对象是只读的

若想禁用当前​select​语句的二级缓存,添加 ​useCache="false"​修改如下:

<select id="getCountByName" parameterType="java.util.Map" resultType="INTEGER" statementType="CALLABLE" useCache="false">

具体流程:

1.当一个​ sqlseesion ​执行了一次​ select​ 后,在关闭此​ session​ 的时候,会将查询结果缓存到二级缓存

2.当另一个​ sqlsession ​执行​ select​ 时,首先会在他自己的一级缓存中找,如果没找到,就回去二级缓存中找,找到了就返回,就不用去数据库了,从而减少了数据库压力提高了性能

注意:

1、如果 ​SqlSession​ 执行了 DML 操作​(insert、update、delete)​,并 ​commit​ 了,那么 ​mybatis​ 就会清空当前​ mapper​ 缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现差异

2、​ mybatis​ 的缓存是基于​ [namespace:sql语句:参数] ​来进行缓存的,意思就是,​SqlSession​ 的 ​HashMap​ 存储缓存数据时,是使用 ​[namespace:sql:参数] ​作为 ​key​ ,查询返回的语句作为 ​value​ 保存的。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐