什么是MyBatis

  • MyBatis是一款基于Java的持久层框架,它提供了一种简单的方式来映射数据库操作到Java对象。它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态SQL,可以严格控制SQL执行性能,灵活度高
  • MyBatis可以使用XML或注解来配置映射原生信息,将POJO映射成数据库中的记录,避免了几乎所有JDBC代码和手动设置参数以获取结果集
  • 通过XML文件或注解的方式将要执行的各种statement配置起来,并通过Java对象和statement中的SQL的动态参数进行映射生成最终执行的SQL语句,最后由MyBatis框架执行SQL并将结果映射为Java对象并返回

说说MyBatis的优点和缺点

  • 优点
    1. 基于SQL语句编程,相当灵活,不会对应用程序或数据库的现有设计造成任何影响,SQL写在XML里,解除SQL与程序代码的耦合(用注解写就另说了),便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用
    2. 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接
    3. 提供应设标签,支持对象与数据库的ORM字段关系映射,提供对象关系映射标签,支持对象关系组件维护
  • 缺点
    1. SQL语句编写的工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求
    2. SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库
    3. XML配置较为繁琐,MyBatis的配置文件中需要编写大量的XML代码来描述SQL语句和映射关系,这可能会使配置文件显得较为繁琐

#{}${}的区别是什么

  • 在MyBatis中,#{}${}都是用来表示参数占位符的。不过它们的使用方式略有不同
    • #{}在SQL语句中表示一个占位符,它可以防止SQL注入攻击,并且可以自动进行参数类型转换。在执行SQL语句时,MyBatis会将参数值以安全的方式设置到SQL语句中。底层使用的是PreparedStatement#{}占位符替换为?
    • ${}在SQL语句中也表示一个占位符,但它不会对参数进行任何处理,直接将参数值拼接到SQL语句中,因此容易引发SQL注入攻击。底层使用的是Statement

当实体类中的属性名和表中的字段名不一致时怎么办

  1. 通过在查询的SQL语句中定义字段名的别名,让其别名与实体类的属性名一致
    1
    2
    3
    <select id=”selectorder” parametertype=”int” resultetype=”com.example.domain.order”>
    select order_id as id, order_no as orderno ,order_price as price form orders where order_id=#{id};
    </select>
  2. 使用resultMap定义字段和属性的映射关系
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <select id="getOrder" parameterType="int" resultMap="orderresultmap">
    select * from orders where order_id=#{id}
    </select>
    <resultMap type=”me.gacl.domain.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>

MyBatis是如何进行分页的?分页插件的原理是什么

  • MyBatis使用RowBounds对象进行分页,它是针对ResultSet结果及执行的内存分页,而非物理分页。可以在SQL内直接拼写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页,例如在原有SQL后面拼写limit
  • 分页插件的基本原理是使用MyBatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的SQL,然后重写SQL,根据dialect方言,添加对应的物理分页语句和物理分页参数

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

  • MyBatis将SQL查询结果封装为目标对象并返回,主要通过以下两个步骤完成
    1. 执行SQL查询语句,获取ResultSet结果集
    2. 根据目标对象的映射方式,将ResultSet结果集中的数据映射到目标对象中
  • MyBatis支持以下几种映射方式
    1. 使用SQL列的别名功能,将列名的别名命名为对象的属性名
      1
      SELECT user_name as userName FROM user WHERE user_id = #{userId}
    2. resultMap映射:指定目标对象和ResultSet结果集中各列之间的映射关系
      1
      2
      3
      4
      5
      <resultMap id="userMap" type="User">
      <id property="id" column="user_id"/>
      <result property="name" column="user_name"/>
      <result property="age" column="user_age"/>
      </resultMap>

如何执行批量插入

  • 首先创建一个简单的insert语句
    1
    2
    3
    <insert id=”insertname”>
    insert into names (name) values (#{value})
    </insert>
  • Java代码中实现批量插入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    List<String> names = new ArrayList<>();
    names.add("fred");
    names.add("barney");
    names.add("betty");
    names.add("wilma");

    SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);

    try {
    NameMapper mapper = sqlSession.getMapper(NameMapper.class);
    for (String name : names) {
    mapper.insertName(name);
    }
    sqlSession.commit();
    } catch (Exception e) {
    e.printStackTrace();
    sqlSession.rollback();
    throw e;
    } finally {
    sqlSession.close();
    }

XML映射文件中,除了常见的select、insert、update、delete标签外,还有哪些标签

  1. <resultMap>:用于定义查询结果与Java对象的映射关系,可以自定义属性的映射关系,包括一对一、一对多等复杂映射关系
  2. <sql>:用于定义可重用的SQL片段,可以将SQL片段抽象出来,供多个SQL语句使用
  3. <include>:用于包含其他XML文件中定义的SQL片段,可以实现SQL的复用
  4. <if>:用于在SQL语句中添加条件判断,可以根据参数动态生成SQL语句
  5. <where>:用于将多个条件组合成一个WHERE子句,避免生成无用的WHERE子句
  6. <foreach>:用于遍历集合或数组,并将元素拼接成SQL语句中的IN子句
  7. <choose><when><otherwise>:用于实现复杂的条件判断,类似Java中的if-else语句
  8. <trim>:用于对SQL语句进行字符串处理,如去除逗号、括号等

MyBatis实现一对一有几种方式?具体怎么操作的

  • MyBatis实现一对一有以下两种方式
    1. 使用<resultMap>标签定义一对一映射关系
      • 例如,我们有一个Order类和一个User类,一个订单只属于一个用户,因此Order类中包含一个User对象作为属性。在XML映射问建中,我们可以定义一个<resultMap>标签,定义Order类与User类之间的映射关系
        1
        2
        3
        4
        5
        6
        <resultMap id="orderMap" type="Order">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="userId" column="user_id"/>
        <association property="user" javaType="User" select="getUserById"/>
        </resultMap>
      • 在上面的标签中,我们使用标签定义Order类中的user属性与User类之间的映射关系。其中,property属性指定Order类中的user属性,javaType属性指定User类的类型,select属性指定通过getUserById查询用户信息的SQL语句。
    2. 使用嵌套查询
      • 使用嵌套查询可以在查询订单的同时查询用户信息,将查询结果组装成一个Order对象,例如我们在XML映射文件中定义以下SQL语句
        1
        2
        3
        4
        5
        6
        <select id="getOrderById" resultMap="orderMap">
        select o.id, o.name, o.user_id, u.username, u.phone, u.address
        from orders o
        join users u on o.user_id = u.id
        where o.id = #{id}
        </select>
      • 在上面的SQL语句中,我们通过join语句关联了orders表和users表,并查询了订单和用户的信息。在<select>标签中,我们通过resultMap属性指定了将查询结果映射到Order对象上的<resultMap>标签。在<resultMap>标签中,我们通过<association>标签定义了Order对象和User对象之间的映射关系。

MyBatis是否支持延迟加载?如果支持,它的实现原理是什么

  • MyBatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的是一对一,collection指的是一对多查询。在MyBatis配置文件中,可以配置是否启用延迟加载:lazyLoadingEnable=true|false
  • MyBatis实现延迟加载的原理是使用代理对象,代理对象在访问对象时触发延迟加载,从而实现按需加载
    1
    2
    3
    4
    5
    <configuration>
    <settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    </settings>
    </configuration>
  • 在MyBatis中,延迟加载的实现主要有两种方式
    1. 延迟加载嵌套查询:在XML映射文件中,我们可以通过使用select标签定义一个嵌套查询,当需要使用到延迟加载对象时,MyBatis会根据该嵌套查询获取相关的数据。例如,以下代码演示了如何通过嵌套查询实现延迟加载
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      <resultMap id="userMap" type="User">
      <id property="id" column="id"/>
      <result property="name" column="name"/>
      <collection property="orders" ofType="Order">
      <id property="id" column="order_id"/>
      <result property="name" column="order_name"/>
      <association property="user" javaType="User" select="getUserById" fetchType="lazy"/>
      </collection>
      </resultMap>

      <select id="getUserById" resultMap="userMap">
      select u.id, u.name, o.id as order_id, o.name as order_name, o.user_id as user_id
      from users u
      left join orders o on o.user_id = u.id
      where u.id = #{id}
      </select>
      • 在上面的XML映射文件中,我们定义了一个User对象和一个Order对象之间的一对多关系,使用collection标签定义User对象中的order属性,其中通过<association>标签的fetchType=lazy属性实现了延迟加载
    2. 延迟加载代理对象:它的原理是,通过CGLIB动态代理生成了一个目标对象的代理对象。当我们调用代理对象的方法时,代理对象内部的拦截器方法会先被执行,这个拦截器会检查当前对象是否已经加载了关联对象。如果没有加载,拦截器会执行预先保存好的查询语句,从数据库中查询关联对象的数据,并将查询结果映射为Java对象。接着,代理对象的拦截器会将查询到的关联对象设置到目标对象中,然后继续执行我们最初调用的方法,以便返回正确的结果。
      • 举个例子:假设我们有一个User对象,它包含一个Order对象的引用。我们在访问User对象的getOrder()方法时,如果关联的Order对象还没有被加载,拦截器会执行查询语句,从数据库中查询Order对象的数据,然后拦截器会将查询到的Order对象设置到User对象中的order属性中。最后拦截器返回Order对象,让我们可以正常地范文它的属性或方法
      • 这样我们就可以通过延迟加载来优化数据库访问,只有需要时才回去查询关联对象,而不是每次访问对象时都进行查询

说说MyBatis的缓存机制

  • MyBatis的缓存机制主要分为一级缓存和二级缓存
    • 一级缓存指的是SqlSession级别的缓存,它默认是开启的,同一个SqlSession内部的多次查询操作,如果查询的参数和查询语句都相同,那么第一次查询时查询结果会被缓存到一级缓存中,后续的查询操作会直接从缓存中获取结果,而不会再去查询数据库。一级缓存的生命周期与SqlSession的生命周期相同,当SqlSession被关闭时,一级缓存也会被清空
    • 二级缓存指的是Mapper级别的缓存,它需要手动开启。开启二级缓存后,查询操作的结果会被缓存到内存或者其他缓存存储设备中,当下次再次查询相同的数据时,会优先从缓存中获取数据,而不是再次查询数据库。不同的SqlSession之间可以共享二级缓存,因此当多个SqlSession操作同一个Mapper时,他们之间共享一个二级缓存,二级缓存的缓存时间是无限制的,但是它的缓存策略是基于LRU(最近最少使用)算法的
    • 需要注意的是,二级缓存中的缓存数据是需要被序列化和反序列化的,因此当我们在Mapper中使用自定义类型时,需要确保这些类型支持序列化操作
    • 为了提高缓存命中率,MyBatis还提供了缓存清空机制,可以通过在Mapper中配置flushCache="true"来清空缓存。此外,MyBatis还支持基于注解的缓存配置,可以通过在Mapper接口或方法上添加@CacheNamespace或者@CacheNamespaceRef注解来配置缓存
  • 当开启缓存后,数据的查询执行流程为:二级缓存 -> 一级缓存 -> 数据库
  • 但是MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻
  • 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis等分布式缓存可能成本更低,安全性也更高

JDBC编程有哪些步骤

  • JDBC是Java数据库连接的标准API,用于连接和操作关系型数据库。JDBC编程的基本步骤如下
    1. 加载数据库驱动程序。使用Class.forName()方法加载对应的数据库驱动程序,如Class.forName("com.mysql.jdbc.Driver")加载MySQL数据库驱动程序
    2. 创建数据库连接,使用DriverManager.getConnection()方法创建数据库连接,需要指定数据库连接字符串、用户名、密码等连接信息,例如
      1
      Connection conn = DirverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "username", "password");
    3. 创建Statement或者PreparedStatement对象,用于执行SQL语句
      1
      2
      Connection.createStatement();
      Connection.prepareStatement();
    4. 执行SQL语句,使用Statement或PreparedStatement对象的execute()executeQuery()executeUpdate()等方法执行SQL语句
    5. 处理查询结果,如果执行的是查询语句,需要使用ResultSet对象获取查询结果
    6. 关闭ResultSet、Statement、Connection独享。释放占用的资源,防止出现内存泄漏

MyBatis中见过什么设计模式

  1. 工厂模式:MyBatis中的SqlSessionFactory和SqlSession都是通过工厂模式创建的,SqlSessionFactoryBuilder类的作用就是创建SqlSessionFactory对象。
  2. 代理模式:MyBatis中的Mapper接口实现类是通过JDK动态代理实现的。在执行Mapper接口中的方法时,实际上是通过动态代理生成一个代理对象,代理对象调用方法是会自动执行相应的SQL语句
  3. 装饰器模式:MyBatis中的插件就是通过装饰器模式实现的,插件可以在不修改原有代码的基础上,增强或修改原有功能
  4. 享元模式:MyBatis中的缓存就是使用了享元模式,通过缓存已经查询过的数据,避免重复查询数据库
  5. 观察者模式:MyBatis中的拦截器就是使用了观察者模式,拦截器可以在SQL执行前后进行一些额外操作

MyBatis中例如UserMapper.java是接口,为什么没有实现类还能调用

  • 在MyBatis中,Mapper接口是由MyBatis框架动态生成的。当我们定义一个Mapper接口时,MyBatis会根据接口的定义和XML映射文件中的配置信息,动态生成一个接口的实现类,并将其注册到Spring容器中,供应用程序使用
  • MyBatis使用了Java动态代理技术来生成Mapper接口的实现类。当应用程序调用Mapper接口中的方法时,实际上是调用了Mapper接口的代理对象的方法。代理对象会根据方法名和参数类型等信息,动态生成相应的SQL语句,并将其执行。由于动态代理是在运行时生成的,因此不需要Mapper接口的实现类
  • 这种通过接口定义方法,而不需要手动实现的方式,成为面向接口编程。它可以降低代码的耦合度,使得应用程序更加灵活和可扩展