《小码哥带你圆梦大厂》30道面试题拿下Mybatis


Mybatis篇

1、Mybatis是什么

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

详解:

MyBatis 是支持普通 SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Ordinary Java Objects,普通的 Java对象)映射成数据库中的记录。

每个MyBatis应用程序主要都是使用SqlSessionFactory实例的,一个SqlSessionFactory实例可以通过SqlSessionFactoryBuilder获得。SqlSessionFactoryBuilder可以从一个xml配置文件或者一个预定义的配置类的实例获得。

用xml文件构建SqlSessionFactory实例是非常简单的事情。推荐在这个配置中使用类路径资源(classpath resource),但你可以使用任何Reader实例,包括用文件路径或file://开头的url创建的实例。MyBatis有一个实用类----Resources,它有很多方法,可以方便地从类路径及其它位置加载资源。

2、Mybatis的优点

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件。易于学习,易于使用。通过文档和源代码,可以比较完全的掌握它的设计思路和实现。

  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。

  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。

  • 提供映射标签,支持对象与数据库的orm字段关系映射。

  • 提供对象关系映射标签,支持对象关系组建维护。

  • 提供xml标签,支持编写动态sql。 [2]

3、Mybatis的运作流程

为了使大家能够更加清晰的理解MyBatis程序,在正式讲解MyBatis入门案例之前,先来了解一下MyBatis程序的工作原理,如图1所示。

从图可以看出,MyBatis框架在操作数据库时,大体经过了8个步骤。下面就对图1中的每一步流程进行详细讲解,具体如下。

(1)读取MyBatis配置文件mybatis-config.xml。mybatis-config.xml作为MyBatis的全局配置文件,配置了MyBatis的运行环境等信息,其中主要内容是获取数据库连接。

(2)加载映射文件Mapper.xml。Mapper.xml文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在mybatis-config.xml中加载才能执行。mybatis-config.xml可以加载多个配置文件,每个配置文件对应数据库中的一张表。

(3)构建会话工厂。通过MyBatis的环境等配置信息构建会话工厂SqlSessionFactory。

(4)创建SqlSession对象。由会话工厂创建SqlSession对象,该对象中包含了执行SQL的所有方法。

(5)MyBatis底层定义了一个Executor接口来操作数据库,它会根据SqlSession传递的参数动态的生成需要执行的SQL语句,同时负责查询缓存的维护。

(6)在Executor接口的执行方法中,包含一个MappedStatement类型的参数,该参数是对映射信息的封装,用来存储要映射的SQL语句的id、参数等。Mapper.xml文件中一个SQL对应一个MappedStatement对象,SQL的id即是MappedStatement的id。

(7)输入参数映射。在执行方法时,MappedStatement对象会对用户执行SQL语句的输入参数进行定义(可以定义为Map、List类型、基本类型和POJO类型),Executor执行器会通过MappedStatement对象在执行SQL前,将输入的Java对象映射到SQL语句中。这里对输入参数的映射过程就类似于JDBC编程中对preparedStatement对象设置参数的过程。

(8)输出结果映射。在数据库中执行完SQL语句后,MappedStatement对象会对SQL执行输出的结果进行定义(可以定义为Map和List类型、基本类型、POJO类型),Executor执行器会通过MappedStatement对象在执行SQL语句后,将输出结果映射至Java对象中。这种将输出结果映射到Java对象的过程就类似于JDBC编程中对结果的解析处理过程。

通过上面对MyBatis框架执行流程的讲解,相信读者对MyBatis框架已经有了一个初步的了解。对于初学者来说,上面所讲解的内容可能不会完全理解,现阶段也不要求读者能完全理解,这里讲解MyBatis框架的执行过程是为了方便后面程序的学习。在学习完MyBatis框架后,读者自然就会明白上面所讲解的内容了。

4、为什么要学习Mybatis

通过上边对mybatis的介绍,mybatis可以做什么,想必心里有点底了,mybatis主要用于j2ee企业级应用开发的持久层,用于处理java程序与数据库之间的交互。

在j2ee企业级应用开发中,其实并不只有mybatis,还有hibernate、ebean、jpa等其他持久层解决方案可以选择,那么为什么要选择使用mybatis呢?其实这个问题答案并不固定,因为,你也可以不用mybatis呀,这里就要多说一句了,互联网技术里边,除了真正存在bug的,其余的每一个技术都有存在的价值,毕竟存在即合理,之前不是有人说:"不选贵的,只选对的",其实在技术选择上也是这个道理,适合的才是最好的,在真正应用开发时,并不是用最新的技术就是最好的,要根据自己将要开发的应用的特点、成本等多个方面进行考量到底使用什么技术。

言归正传,那么哪种项目更加适合选择使用mybatis呢?这里拿hibernate来比较一下,因为纯介绍mybatis的有特点可能感觉不出什么,hibernate和mybatis是使用最多的两个持久层框架,比较一下选择时可能会更清晰,接下来将在对ORM的支持、灵活性、开发效率、适用场景四个方面进行阐述。

hibernate是全ORM框架,mybatis是半ORM框架,需要自己写sql,其实这就是这个问题的根源,正因为特性不一样,因此就自然而然的导致了其他地方不一样。因为hibernate是全ORM框架,因此在映射规则确定的情况下只需要操作对应的java数据模型即可,因此用起来开发效率是高的(因为不需要自动维护一堆sql呀,只需要直接调用内置的方法即可),也正因为这样,映射规则的确定是复杂的,前期对象之间关系固定后,后期是不容易改变的,而转言mybatis,因为是半ORM框架,sql是自己写的,对应的映射规则也是相应维护的,因此后期如果数据模型和之间的关系改变了,mybatis改变起来更加简单;也正因为上述的,mybatis也更加灵活,正因为sql可以自己开发,因此在优化sql上更容易实现,而hibernate就不见得了;不过在开发效率上,在适合自己的领域,hibernate框架肯定是开发效率更胜一筹。

通过上边说到的,其实最本质的核心也就一个,mybatis可以自己写sql,因此灵活性更高,因此在开发一些业务经常调整的项目时可以使用,例如:电商,而hibernate则更加适合于一些业务模式固化的项目,例如:OA。

5、什么是动态sql

一、简单来说就是在我们的xml文件中根据需求动态的拼接sql语句使用步驟

1.新建数据表
CREATE TABLE `blog` (
`id` varchar(50) NOT NULL COMMENT '牛客id',
`title` varchar(100) NOT NULL COMMENT '牛客标题',
`author` varchar(30) NOT NULL COMMENT '牛客作者',
`create_time` datetime NOT NULL COMMENT '创建时间',
`views` int(30) NOT NULL COMMENT '浏览量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8
2.IDutil工具类
代码如下(示例):
public class IDUtil {

   public static String genId(){
       return UUID.randomUUID().toString().replaceAll("-","");
  }

}
3 实体类
@Data public class Blog {

   private String id;
   private String title;
   private String author;
   private Date createTime;
   private int views;

}
4、编写Mapper接口及xml文件
public interface BlogMapper {
}
<?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="com.kuang.dao.BlogMapper">

</mapper>
5、mybatis核心配置文件,下划线驼峰自动转换
<settings>
   <setting name="mapUnderscoreToCamelCase" value="true"/>
   <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--注册Mapper.xml-->
<mappers>
 <mapper class="com.kuang.dao.BlogMapper"/>
</mappers>

插入初始数据

6、接口
//新增一个blog int addBlog(Blog blog);
7、sql配置文件
<insert id="addBlog" parameterType="blog">
  insert into blog (id, title, author, create_time, views)
  values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
8、测试
@Test
public void addInitBlog(){
   SqlSession session = MybatisUtils.getSession();
   BlogMapper mapper = session.getMapper(BlogMapper.class);

   Blog blog = new Blog();
   blog.setId(IDUtil.genId());
   blog.setTitle("Mybatis");
   blog.setAuthor("小马哥");
   blog.setCreateTime(new Date());
   blog.setViews(9999);

   mapper.addBlog(blog);

   blog.setId(IDUtil.genId());
   blog.setTitle("码出宇宙");
   mapper.addBlog(blog);

   blog.setId(IDUtil.genId());
   blog.setTitle("菜鸟刷题");
   mapper.addBlog(blog);

   blog.setId(IDUtil.genId());
   blog.setTitle("牛客刷题");
   mapper.addBlog(blog);

   session.close();
}


if语句

需求:根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询

1、接口
//需求1 List<Blog> queryBlogIf(Map map);
2、编写sql
<!--需求1:
根据作者名字和博客名字来查询博客!
如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询
select * from blog where title = #{title} and author = #{author}
-->
<select id="queryBlogIf" parameterType="map" resultType="blog">
  select * from blog where
   <if test="title != null">
      title = #{title}
   </if>
   <if test="author != null">
      and author = #{author}
   </if>
</select>
3、测试
@Test
public void testQueryBlogIf(){
   SqlSession session = MybatisUtils.getSession();
   BlogMapper mapper = session.getMapper(BlogMapper.class);

   HashMap<String, String> map = new HashMap<String, String>();
   map.put("title","Mybatis如此简单");
   map.put("author","小马哥");
   List<Blog> blogs = mapper.queryBlogIf(map);

   System.out.println(blogs);

   session.close();
}

这样写我们可以看到,如果 author 等于 null,那么查询语句为 select * from user where title=#{title},但是如果title为空呢?那么查询语句为 select * from user where and author=#{author},这是错误的 SQL 语句,如何解决呢?请看下面的 where 语句!

4、where
修改上面的SQL语句;
@Test
public void testQueryBlogIf(){
   SqlSession session = MybatisUtils.getSession();
   BlogMapper mapper = session.getMapper(BlogMapper.class);

   HashMap<String, String> map = new HashMap<String, String>();
   map.put("title","Mybatis如此简单");
   map.put("author","小马哥");
   List<Blog> blogs = mapper.queryBlogIf(map);

   System.out.println(blogs);

   session.close();
}

这个"where"标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。

set

同理,上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?

1、编写接口方法
int updateBlog(Map map);
2、sql配置文件
<!--注意set是用的逗号隔开-->
<update id="updateBlog" parameterType="map">
  update blog
     <set>
         <if test="title != null">
            title = #{title},
         </if>
         <if test="author != null">
            author = #{author}
         </if>
     </set>
  where id = #{id};
</update>
3、测试
@Test
public void testUpdateBlog(){
   SqlSession session = MybatisUtils.getSession();
   BlogMapper mapper = session.getMapper(BlogMapper.class);

   HashMap<String, String> map = new HashMap<String, String>();
   map.put("title","动态SQL");
   map.put("author","小马哥");
   map.put("id","9d6a763f5e1347cebda43e2a32687a77");

   mapper.updateBlog(map);

   session.close();
}

choose

有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句

1、编写接口方法

choose 有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句 1、编写接口方法

List<Blog> queryBlogChoose(Map map);
2、sql配置文件
<select id="queryBlogChoose" parameterType="map" resultType="blog">
  select * from blog
   <where>
       <choose>
           <when test="title != null">
                title = #{title}
           </when>
           <when test="author != null">
              and author = #{author}
           </when>
           <otherwise>
              and views = #{views}
           </otherwise>
       </choose>
   </where>
</select>

3、测试类
@Test
public void testQueryBlogChoose(){
   SqlSession session = MybatisUtils.getSession();
   BlogMapper mapper = session.getMapper(BlogMapper.class);

   HashMap<String, Object> map = new HashMap<String, Object>();
   map.put("title","Mybatis");
   map.put("author","小马哥");
   map.put("views",9999);
   List<Blog> blogs = mapper.queryBlogChoose(map);

   System.out.println(blogs);

   session.close();
}

SQL片段

有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

SQL片段 有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

1、提取SQL片段:
<sql id="if-title-author">
   <if test="title != null">
      title = #{title}
   </if>
   <if test="author != null">
      and author = #{author}
   </if>
</sql>
2、引用sql片段
<select id="queryBlogIf" parameterType="map" resultType="blog">
  select * from blog
   <where>
       <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
       <include refid="if-title-author"></include>
       <!-- 在这里还可以引用其他的 sql 片段 -->
   </where>
</select>

注意:

①、最好基于 单表来定义 sql 片段,提高片段的可重用性

②、在 sql 片段中不要包括 where

Foreach

将数据库中前三个数据的id修改为1,2,3;

需求:我们需要查询 blog 表中 id 分别为1,2,3的博客信息

1、编写接口
List<Blog> queryBlogForeach(Map map);
2、编写sql语句
<select id="queryBlogForeach" parameterType="map" resultType="blog">
  select * from blog
   <where>
       <!--
       collection:指定输入对象中的集合属性
       item:每次遍历生成的对象
       open:开始遍历时的拼接字符串
       close:结束时拼接的字符串
       separator:遍历对象之间需要拼接的字符串
       select * from blog where 1=1 and (id=1&nbs***bsp;id=2&nbs***bsp;id=3)
     -->
       <foreach collection="ids"  item="id" open="and (" close=")" separator="or">
          id=#{id}
       </foreach>
   </where>
</select>
3、测试
@Test
public void testQueryBlogForeach(){
   SqlSession session = MybatisUtils.getSession();
   BlogMapper mapper = session.getMapper(BlogMapper.class);

   HashMap map = new HashMap();
   List<Integer> ids = new ArrayList<Integer>();
   ids.add(1);
   ids.add(2);
   ids.add(3);
   map.put("ids",ids);

   List<Blog> blogs = mapper.queryBlogForeach(map);

   System.out.println(blogs);

   session.close();
}

小结

其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错。多在实践中使用才是熟练掌握它的技巧。

6、Mybatis框架中xml文件中常用的标签

除了常见的<select><insert><update> <delete>标签以外,

还有<resultMap><parameterMap>(以被弃用)、<sql> <include> <selectKey>

加上动态sql的9个标签,trim、where、set、foreach、if、choose、when、otherwise、bind等,

其中<sql>为sql片段标签,通过<include>标签引入sql片段,<selectKey>为不支持自增的主键生成策略标签。

一、常见标签

1、select
 <select id="queryUserById" resultType="User">
     select id,username,password,gender,regist_time
     from t_user
     where id = #{arg0}
 </select>
     
 <select id="queryUserByIdAndUsername" resultType="User">
     select *from t_user where id = #{id} and username = #{username}
 </select>
2、insert和selectKey
<insert id="insertUser" parameterType="User">
insert into t_user(username,password,gender,birth)
values (username=#{username},password=#{password},gender=#{gender},birth=#{birth})
</insert>
    
<insert id="insertUser" parameterType="User">
<selectKey order="AFTER" resultType="int" keyProperty="id">
	select last_insert_id()
</selectKey>
insert into t_user(username,password,gender,birth)
values (username=#{username},password=#{password},gender=#{gender},birth=#{birth})
</insert>
    
<insert id="insertUser" parameterType="User">
<selectKey order="BEFORE" resultType="String" keyProperty="id">
	select replace(UUID(),"_","")
</selectKey>
insert into t_user(username,password,gender,birth)
values (username=#{username},password=#{password},gender=#{gender},birth=#{birth})
</insert>
3、update
<update id="updateUser" parameterType="User">
	update t_user
	set username=#{username},password=#{password},gender=#{gender},birth=#{birth}
	where id = #{id}
</update>
4、delete
<delete id="deleteById" parameterType="int">
	delet from t_user
	where id = #{id}
</delete>
5、resultMap
<resultMap id="rm" type="User">
	<id column="id" property="id"></id>
	<result column="password" property="password"></result>
	<result column="username" property="username"></result>
	<result column="gender" property="gender"></result>
	<!--描述自定义属性类passengers:id nationality expire 和passport的映射规则-->
	<association property="address" javaType="Address">
		<id colum="aid" property="aid"></id>
		<result column="address_info" property="address_info"></result>
	</association>
	<!--描述集合的映射-->
	<collection property="order" ofType="Order">
		<id colum="oid" property="oid"></id>
		<result column="product" property="product"></result>
	</collection>
</resultMap>

<select id="queryUser" resultMap="tm">
	select u.id,u.username,u.password,u.gender,a.aid,a.address_info,o.oid,o.product
	from t_user u join t_address a
	on u.id = a.uid
	join t_order o
	on u.id = o.uid
	where id=#{id}
</select>
6、sql和include
<sql id="hhh">select *from t_user</sql>
<select id="queryUserByid" resultType="User">
	<include refid="hhh"/>
	where id=#{id}
</select>

二、动态sql的9个标签

1、if
<sql id="hhh">select *from t_user</sql>
   <select id="queryUser" resultType="User">
		<include refid="hhh"/>
		where 
		<if test="username!=null">
		username=#{username}
		</if>
		<if test="password!=null">
		password=#{password}
		</if>
    </select>
2、where
<sql id="hhh">select *from t_user</sql>   
   <select id="queryUserByUnameAndPwd resultType="User">
		<include refid="hhh"/>
		<where>
			<if test="username!=null">
				username = #{username}
			</if>
			<if test="password!=null">
				and password = #{password}
			</if>
		</where>
    </select>
3、set
<update id="updateUser" parameterType="User">
	update t_user
	<set>
	<if test="password!=null">
	password = #{password},
	</if>
	<if test="username!=null">
	username=#{username}
	</if>
	where id = #{id}
</update>

以上的逗号username因为是最后一个不加逗号

4、trim
 <select id="queryUserById2" resultType="User">
        <include refid="user_fired"/>
        <!--prefix="where" 前缀为where,
        prefixOverrides="or|and" where子句中如果以or或者and开头,会被覆盖-->
        
 <trim prefix="where" prefixOverrides="or|and">
        <if test="username!=null">
            username = #{username}
        </if>
        <if test="gender!=null">
            and gender = #{gender}
        </if>
    </trim>
</select>
<update id="updateUserById" parameterType="User">
    update t_user
    <!--prefix="set" 前缀为set,
    suffixOverrides=","自动将最后一个逗号删除-->

    <trim prefix="set" suffixOverrides=",">
        <if test="username!=null">
            username = #{username},
        </if>
        <if test="gender!=null">
            gender = #{gender},
        </if>
    </trim>
    where id = #{id}
</update>
5、foreach
<delete id="deleteUsersById" parameterType="java.util.List">
	delete from t_user where id in
	<foreach collection="list" open="(" close=")" separator="," item="i">
	#{i}
	</foreach>
</delete>
<insert id="insertUsers" parameterType="java.util.List">
    insert into t_user values
    <foreach collection="list" open="" close="" separator="," item="u">
        (null,#{u.username},#{u.password},#{u.gender},#{u.registTime})
    </foreach>
</insert>


6、choose和when和otherwise
  <select id="findUser" resultType="User">
        select *from t_user where id = #{id}
        <choose>
            <when test="username!=null">
                AND username like #{username}
            </when>
            <when test="password!=null">
                AND password = #{password}
            </when>
            <otherwise>
                AND gender = 1
            </otherwise>
        </choose>
    </select>
7、bind
<?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="com.how2java.pojo">
        <!-- 本来的模糊查询方式 -->
<!--         <select id="listProduct" resultType="Product"> -->
<!--             select * from   product_  where name like concat('%',#{0},'%') -->
<!--         </select> -->


        <select id="listProduct" resultType="Product">
            <bind name="likename" value="'%' + name + '%'" />
            select * from   product_  where name like #{likename}
        </select>

</mapper>
8、when
  <select id="findUser" resultType="User">
        select *from t_user where id = #{id}
        <choose>
            <when test="username!=null">
                AND username like #{username}
            </when>
            <when test="password!=null">
                AND password = #{password}
            </when>
            <otherwise>
                AND gender = 1
            </otherwise>
        </choose>
    </select>
9、otherwise
  <select id="findUser" resultType="User">
        select *from t_user where id = #{id}
        <choose>
            <when test="username!=null">
                AND username like #{username}
            </when>
            <when test="password!=null">
                AND password = #{password}
            </when>
            <otherwise>
                AND gender = 1
            </otherwise>
        </choose>
    </select>

7、Mybatis中的事务处理:(单)

利用MyBatis框架的配置管理比直接使用JDBC API编写事务控制要来得更加轻松,这里我们就来详解Java的MyBatis框架中的事务处理,尤其是和Spring框架集成后更加exciting

1.MyBatis单独使用时,使用SqlSession来处理事务:
public class MyBatisTxTest {

  private static SqlSessionFactory sqlSessionFactory;
  private static Reader reader;

  @BeforeClass
  public static void setUpBeforeClass() throws Exception {
    try {
      reader = Resources.getResourceAsReader("Configuration.xml");
      sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    } finally {
      if (reader != null) {
        reader.close();
      }
    }
  }

  @Test
  public void updateUserTxTest() {
    SqlSession session = sqlSessionFactory.openSession(false); // 打开会话,事务开始
     

    try {
      IUserMapper mapper = session.getMapper(IUserMapper.class);
      User user = new User(9, "Test transaction");
      int affectedCount = mapper.updateUser(user); // 因后面的异常而未执行commit语句
      User user = new User(10, "Test transaction continuously");
      int affectedCount2 = mapper.updateUser(user2); // 因后面的异常而未执行commit语句
      int i = 2 / 0; // 触发运行时异常
      session.commit(); // 提交会话,即事务提交
    } finally {
      session.close(); // 关闭会话,释放资源
    }

  }
}

8、Mybatis的xml配置的顺序

<?xml version="1.0" encoding="UTF-8"?>
<configuration><!--配置-->
	<properties/><!--属性-->
	<settings/><!--设置-->
	<typeAliases/><!--类型别名--> 
	<typeHandlers/><!--类型处理器--> 
	<objectFactory/><!--对象工厂-->  
	<plugins/><!--插件--> 
	<environments><!--配置环境--> 
		<environment><!--环境变量--> 
		<transactionManager/><!--事务管理器--> 
			<dataSource/><!--数据源--> 
		</environment>
	</environments>
	<databaseidProvider/><!--数据库厂商标识-->  
	<mappers/><!--映射器--> 
</configuration>

9、Mybatis核心依赖

<dependencies>
        <!--Mybatis核心-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>

        <!--junit测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    
        <!--Mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.3</version>
        </dependency>
    
        <!--log4j-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>

</dependencies>

10、日志工厂

如果一个数据库操作,出现了异常,我们需要排错,日志就是最好的助手

曾经:sout,debug

现在:日志工厂!

  1. SLF4J

  2. Apache Commons Logging

  3. Log4j 2

  4. Log4j

  5. JDK logging

  6. STDOUT_LOGGING

  7. NO_LOGGING

在MyBatis中具体使用哪一个日志实现,在设置中设定。

STDOUT_LOGGING标准日志输出 在mybatis核心配置文件中配置我们的日志

<settings>  <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>

img

img

1、 LOG4J

什么是Log4j?

Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程

我们也可以控制每一条日志的输出格式

通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程

通过一个配置文件来灵活地进行配置,而不需要修改应用的代码

1 导入依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
2 配置log4j.properties
#### 设置

log4j.rootLogger = DEBUG,console,file

#### 输出信息到控制台 ####

log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold = DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

#### 输出DEBUG 级别以上的日志到=该项目下/logs/kwok.log ####

log4j.appender.file = org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.File = ./logs/kwok.log
log4j.appender.file.MaxFileSize = 10mb
log4j.appender.file.Threshold = DEBUG
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

#### 输出ERROR 级别以上的日志到=该项目下/logs/error.log ####

log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File = ./logs/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

#### 日志输出级别

log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
3 配置log4j为日志的实现
<settings>
  <!--标准的日志工厂实现-->
  <!--<setting name="logImpl" value="STDOUT_LOGGING"/>-->
  <setting name="logImpl" value="LOG4J"/>
</settings>
4 Log4j的使用!直接测试运行已有的查询

img

5 简单使用

在要使用Log4j的类中,导入包

import org.apache.log4j.Logger;

日志对象,参数为当前类的class

static Logger logger = Logger.getLogger(StudentDaoTest.class);
日志级别
logger.info("info:进入了testLog4j"); logger.debug("debug:进入了testLog4j");  logger.error("error:进入了testLog4j");

img

日志打印
[INFO ] 2021-11-16 12:12:13,627 method:com.kwok.dao.student.StudentDaoTest.testLog4j(StudentDaoTest.java:24) info:进入了testLog4j [DEBUG] 2021-11-16 12:12:13,629 method:com.kwok.dao.student.StudentDaoTest.testLog4j(StudentDaoTest.java:25) debug:进入了testLog4j [ERROR] 2021-11-16 12:12:13,630 method:com.kwok.dao.student.StudentDaoTest.testLog4j(StudentDaoTest.java:26) error:进入了testLog4j

log日志问号文件打不开 解决:不要在mappers或typeAliases中使用package来指定要扫描的包

img

img

11、一对多,多对一

本次例子要完成的是Mybatis关系映射中一对多的关系:比如一个学生对应一个专业,一个专业对应多个学生。

需求:1、根据学号查找该学生以及用查询后学生的专业外键值查出该生专业对象

2、根据专业id值查出专业以及所有属于该专业的学生

mybatis的映射实现方法有两种:1、通过一条sql语句的映射和自定义的resultMap结果集得到一个包含专业对象的学生对象结果或一个包含学生集合对象的专业对象结果集。如:select * from stuinfo s,major m where s.sno=#{sno} and s.majorId=m.id

2、通过两条sql语句的先后执行(其中一条查询的条件为另一个的执行结果)

如:先查询出学生对象select * from stuinfo where sno=#{sno},再通过外键查出专业:select * from major where id=#{majorId}

mybatis 中映射的实现方法:
<select id="getStuAndMajor" resultMap="stuAndMajor" parameterType="String">
      select * from stuinfo where sno=#{sno}
</select>

<resultMap type="Stuinfo" id="stuAndMajor">
      <id property="sno" column="sno"/>
      <result property="sname" column="sname"/>
      <association property="major" javaType="Major" select="getMajor" column="majorId"/>
</resultMap>


<select id="getMajor" resultType="Major" parameterType="int">
      select * from major where id=#{majorId}
</select>

数据库stuinfo表:

major表:

其中学生表外键majorId是专业表的主键

1、项目目录结构如下

2、Major实体类
public class Major {
    private int id;

    private String majorName;

    private int personNum;

    private String turtor;

    private List<Stuinfo> students; @Override public String toString() {
        return "Major{" +
                "id=" + id +
                ", majorName='" + majorName + ''' +
                ", personNum=" + personNum +
                ", turtor='" + turtor + ''' +
                ", students=" + students +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getMajorName() {
        return majorName;
    }

    public void setMajorName(String majorName) {
        this.majorName = majorName;
    }

    public int getPersonNum() {
        return personNum;
    }

    public void setPersonNum(int personNum) {
        this.personNum = personNum;
    }

    public String getTurtor() {
        return turtor;
    }

    public void setTurtor(String turtor) {
        this.turtor = turtor;
    }

    public List<Stuinfo> getStudents() {
        return students;
    }

    public void setStudents(List<Stuinfo> students) {
        this.students = students;
    }
}
3、Stuinfo实体类
public class Stuinfo  {
    private String sno;

    private String sname;

    private int gender;

    private Data birthday;

    private int majorId;

    private Major major;

    private int credit;

    private String remark; @Override public String toString() {
        return "Stuinfo{" +
                "sno='" + sno + ''' +
                ", sname='" + sname + ''' +
                ", gender=" + gender +
                ", birthday=" + birthday +
                ", majorId=" + majorId +
                ", major=" + major +
                ", credit=" + credit +
                ", remark='" + remark + ''' +
                '}';
    }

    public String getSno() {
        return sno;
    }

    public void setSno(String sno) {
        this.sno = sno;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }

    public int getGender() {
        return gender;
    }

    public void setGender(int gender) {
        this.gender = gender;
    }

    public Data getBirthday() {
        return birthday;
    }

    public void setBirthday(Data birthday) {
        this.birthday = birthday;
    }

    public int getMajorId() {
        return majorId;
    }

    public void setMajorId(int majorId) {
        this.majorId = majorId;
    }

    public Major getMajor() {
        return major;
    }

    public void setMajor(Major major) {
        this.major = major;
    }

    public int getCredit() {
        return credit;
    }

    public void setCredit(int credit) {
        this.credit = credit;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }
}
4、mybatis.xml
<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
 <configuration>
 	<properties resource="mysql.properties"/>
 	<environments default="development">
 		<environment id="development">
 			<transactionManager type="JDBC"/>
 			<dataSource type="POOLED">
 				<property name="driver" value="${driver}"/>
 				<property name="url" value="${url}"/>
 				<property name="username" value="${user}"/>
 				<property name="password" value="${password}"/
 			</dataSource>
 		</environment>
 	</environments>

 	<mappers>
 		<mapper resource="com/onetomany/mapper/majorMapper.xml"/>
 		<mapper resource="com/onetomany/mapper/StuinfoMapper.xml"/>		
 	</mappers>
 </configuration>
4、majorMapper.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="com.onetomany.mapper.InterDaoMapper">
	<resultMap id="majorMap" type="com.onetomany.model.Major">
		<id property="id" column="id" javaType="java.lang.Integer"/>
		<result property="majorName" column="majorName" javaType="java.lang.String"/>
		<result property="personNum" column="personNum" javaType="java.lang.Integer"/>
		<result property="turtor" column="turtor" javaType="java.lang.String"/>

		<!-- 对象属性集合返回值设置 -->

		<collection property="students" ofType="com.onetomany.model.Stuinfo">
			<id property="sno" column="sno" javaType="java.lang.String"/><!-- id列 -->
			<result property="sname" column="sname" javaType="java.lang.String"/>
			<result property="gender" column="gender" javaType="java.lang.Integer"/>
			<result property="birthday" column="birthday" javaType="java.util.Date"/>
			<result property="credit" column="credit" javaType="java.lang.Integer"/>
			<result property="remark" column="remark" javaType="java.lang.String"/>
		</collection>
	</resultMap>
    
	<select id="getMajorStudents" parameterType="int" resultMap="majorMap">
		select * from major m,stuinfo s where m.id=#{id} and s.majorId=#{id}
	</select>
</mapper>
5、StuinfoMapper.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="com.onetomany.mapper.InterDaoMapper">
	<!-- 自定义返回的结果集 -->
	<resultMap id="stuMap" type="com.onetomany.model.Stuinfo">
		<id property="sno" column="sno" javaType="java.lang.String"/><!-- id列 -->
		<result property="sname" column="sname" javaType="java.lang.String"/>
		<result property="gender" column="gender" javaType="java.lang.Integer"/>
		<result property="majorId" column="majorId" javaType="java.lang.Integer"/>
		<result property="birthday" column="birthday" javaType="java.util.Date"/>
		<result property="credit" column="credit" javaType="java.lang.Integer"/>
		<result property="remark" column="remark" javaType="java.lang.String"/>
        
		<association property="major" javaType="com.onetomany.model.Major"><!-- 关联对象属性 -->
			<id property="id" column="id" javaType="java.lang.Integer"/>
			<result property="majorName" column="majorName" javaType="java.lang.String"/>
			<result property="personNum" column="personNum" javaType="java.lang.Integer"/>
			<result property="turtor" column="turtor" javaType="java.lang.String"/>
		</association>
	</resultMap>

	<!-- 映射接口方法 -->
	<select id="getStuinfo" parameterType="String" resultMap="stuMap">
		select * from stuinfo s,major m where s.sno=#{sno} and s.majorId=m.id
	</select>
</mapper>
6、DBtool
public class DBtool {

	private static SqlSessionFactory sqlSessionFactory;
	static{
		try {
			Reader reader=Resources.getResourceAsReader("mybatis.cfg.xml");
			sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}


	public static SqlSession getSqlSession(){
		return sqlSessionFactory.openSession();
	}
}
7、InterDaoMapper
public interface InterDaoMapper {
	public Stuinfo getStuinfo(String sno);	//通过学号获取学生对象包括其专业对象
	public Major getMajorStudents(int id);//通过id值获得该专业的所有学生
}
8、MapperImpl
public class MapperImpl implements InterDaoMapper { @Override public Stuinfo getStuinfo(String sno) {
	SqlSession sqlSession=DBtool.getSqlSession();
		InterDaoMapper mapper=sqlSession.getMapper(InterDaoMapper.class);
		Stuinfo stu=mapper.getStuinfo(sno);
		sqlSession.commit();
		sqlSession.close();
		return stu;
	} @Override public Major getMajorStudents(int id) {
		SqlSession sqlSession=DBtool.getSqlSession();
		InterDaoMapper mapper=sqlSession.getMapper(InterDaoMapper.class);
		Major major=mapper.getMajorStudents(id);
		sqlSession.commit();
		sqlSession.close();
		return major;
	}
}
9、test
public class Test {
	public static void main(String[] args) {
		InterDaoMapper interDaoMapper=new MapperImpl();
		Stuinfo student=interDaoMapper.getStuinfo("081005");
		System.out.println(student);
		Major major=interDaoMapper.getMajorStudents(15);
		System.out.println(major);
	}
}

10、结果

12、Mybatis动态代理

动态代理实战

众所周知哈,Mybatis 底层封装使用的 JDK 动态代理。说 Mybatis 动态代理之前,先来看一下平常我们写的动态代理 Demo,抛砖引玉

一般来说定义 JDK 动态代理分为三个步骤,如下所示

  1. 定义代理接口

  2. 定义代理接口实现类

  3. 定义动态代理调用处理器

三步代码如下所示,玩过动态代理的小伙伴看过就能明白
public interface Subject { // 定义代理接口
    String sayHello();
}

public class SubjectImpl implements Subject {  // 定义代理接口实现类 @Override public String sayHello() {
        System.out.println(" Hello World");
        return "success";
    }
}

public class ProxyInvocationHandler implements InvocationHandler {  // 定义动态代理调用处理器
    private Object target;

    public ProxyInvocationHandler(Object target) {
        this.target = target;
    } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("    进入代理调用处理器 ");
        return method.invoke(target, args);
    }

}

写个测试程序,运行一下看看效果,同样是分三步

  1. 创建被代理接口的实现类

  2. 创建动态代理类,说一下三个参数

  3. 类加载器

  4. 被代理类所实现的接口数组

  5. 调用处理器(调用被代理类方法,每次都经过它)

  6. 被代理实现类调用方法
public class ProxyTest {
    public static void main(String[] args) {
        Subject subject = new SubjectImpl();
        Subject proxy = (Subject) Proxy
                .newProxyInstance(
                        subject.getClass().getClassLoader(),
                        subject.getClass().getInterfaces(),
                        new ProxyInvocationHandler(subject));

        proxy.sayHello();
        /**
         * 打印输出如下
         * 调用处理器:   进入代理调用处理器
         * 被代理实现类:Hello World
         */
    }

}

Demo 功能实现了,大致运行流程也清楚了,下面要针对原理实现展开分析

动态代理原理分析

从原理的角度上解析一下,上面动态代理测试程序是如何执行的

第一步简单明了,创建了 Subject 接口的实现类,也是我们常规的实现

第二步是创建被代理对象的动态代理对象。这里有朋友就问了,怎么证明这是个动态代理对象?如图所示

JDK 动态代理对象名称是有规则的,凡是经过 Proxy 类生成的动态代理对象,前缀必然是 $Proxy,后面的数字也是名称组成部分

如果有小伙伴想要一探究竟,关注 Proxy 内部类 ProxyClassFactory,这里会有想要的答案

回归正题,继续看一下 ProxyInvocationHandler,内部保持了被代理接口实现类的引用,invoke 方法内部使用反射调用被代理接口实现类方法

可以看出生成的动态代理类,继承了 Proxy 类,然后对 Subject 接口进行了实现,而实现方法 sayHello 中实际调用的是 ProxyInvocationHandler 的 invoke 方法

一不小心发现了 JDK 动态代理不能对类进行代理的原因 ^ ^

也就是说,当我们调用 Subject#sayHello 时,方法调用链是这样的

但是,Demo 里有被代理接口的实现类,Mybatis Mapper 没有,这要怎么玩

不知道不要紧,知道了估计也看不到这了,一起看下 mybatis 源码是怎么玩的

mybatis version:3.4.x

13、Mybatis 代理源码实现

不知道大家考没考虑过这么一个问题,Mapper Mapper 为什么不需要实现类?

假如说,我们项目使用的三层设计,Controller 控制请求接收,Service 负责业务处理,Mapper 负责数据库交互

Mapper 层也就是我们常说的数据库映射层,负责对数据库的操作,比如对数据的查询或者新增、删除等

大胆设想下,项目没有使用 Mybatis,需要在 Mapper 实现层写数据库交互,会写一些什么内容?

会写一些常规的 JDBC 操作,比如:
// 装载Mysql驱动
Class.forName(driveName);
// 获取连接
con = DriverManager.getConnection(url, user, pass);
// 创建Statement
Statement state = con.createStatement();
// 构建SQL语句
String stuQuerySqlStr = "SELECT * FROM student";
// 执行SQL返回结果
ResultSet result = state.executeQuery(stuQuerySqlStr);
...

如果项目中所有 Mapper 实现层都要这么玩,那岂不是很想打人...

所以 Mybatis 结合项目痛点,应运而生,怎么做的呢

将所有和 JDBC 交互的操作,底层采用 JDK 动态代理封装,使用者只需要自定义 Mapper 和 .xml 文件 SQL 语句定义在 .xml 文件或者 Mapper 中,项目启动时通过解析器解析 SQL 语句组装为 Java 中的对象

解析器分为多种,因为 Mybatis 中不仅有静态语句,同时也包含动态 SQL 语句

这也就是为什么 Mapper 接口不需要实现类,因为都已经被 Mybatis 通过动态代理封装了,如果每个 Mapper 都来一个实现类,臃肿且无用。经过这一顿操作,展示给我们的就是项目里用到的 Mybatis 框架

上面铺垫这么久,终于要到主角了,为什么 Mybatis Mapper 接口没有实现类也可以实现动态代理

想要严格按照先后顺序介绍 Mybatis 动态代理流程,而不超前引用未介绍过的术语,这几乎是不可能的,笔者尽量说的通俗易懂

无实现类完成动态代理 核心点来了,拿起小本本坐板正了

我们先来看下普通动态代理有没有可能不用实现类,仅靠接口完成
public interface Subject {
    String sayHello();
}

public class ProxyInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("    进入代理调用处理器 ");
        return "success";
    }

}

根据代码可以看到,我们并没有实现接口 Subject,继续看一下怎么实现动态代理
public class ProxyTest {
    public static void main(String[] args) {
        Subject proxy = (Subject) Proxy
                .newProxyInstance(
                        subject.getClass().getClassLoader(),
                        new Class[]{Subject.class},
                        new ProxyInvocationHandler());

        proxy.sayHello();
        /**
         * 打印输出如下
         * 调用处理器:   进入代理调用处理器
         */
    }

}

可以看到,对比文初的 Demo,这里对 Proxy.newProxyInstance 方法的参数作出了变化

之前是通过实现类获取所实现接口的 Class 数组,而这里是把接口本身放到 Class 数组中,殊归同途

有实现接口和无实现接口产生的动态代理类有什么区别

  1. 有实现接口是对 InvocationHandler#invoke 方法调用,invoke 方法通过反射调用被代理对象(SubjectImpl)方法(sayHello)

  2. 无实现接口则是仅对 InvocationHandler#invoke 产生调用。所以有接口实现返回的是被代理对象接口返回值,而无实现接口返回的仅是 invoke 方法返回值

InvocationHandler#invoke 方法返回值是 success 字符串,定义个字符串变量,是否能成功返回

现在第一个问题答案已经浮现,Mapper 没有实现类,所有调用 JDBC 等操作都是在 Mybatis InvocationHandler 实现的

问题既然已经得到了解决,给人一种感觉,好像没那么难,但是你不好奇,Mybatis 底层怎么做的么?

先抛出一个问题,然后带着问题去看源码,可能让你记忆 Double 倍深刻

咱们 Demo 里的接口是固定的,Mybatis Mapper 可是不固定的,怎么搞?

Mybatis 是这么说的

看看 Mybatis 底层它怎么实现的动态接口代理,小伙伴只需要关注标记处的代码即可

和我们的 Demo 代码很像,核心点在于 mapperInterface 它是怎么赋值的

先来说一下 Mybatis 代理工厂中具体生成动态代理类具体逻辑

  • 根据 .xml 上关联的 namespace, 通过 Class#forName 反射的方式返回 Class 对象(不止 .xml namespace 一种方式)

  • 将得到的 Class 对象(实际就是接口对象)传递给 Mybatis 代理工厂生成代理对象,也就是刚才 mapperInterface 属性

谜底揭晓,Mybatis 使用接口全限定名通过 Class#forName 生成 Class 对象,这个 Class 对象类型就是接口

为了方便大家理解,通过 Mybatis 源码提供的测试类举例。假设已有接口 AutoConstructorMapper 以及对应的 .xml 如下

执行第一步,根据 .xml namespace 得到 Class 对象

  1. 首先第一步获取 .xml 上 mapper 标签 namespace 属性,得到 mapper 接口全限定信息

  2. 根据 mapper 全限定信息获取 Class 对象

  3. 添加到对应的映射器容器中,等待生成动态代理对象

如果此时调用生成动态代理对象,代理工厂 newInstance 方法如下:

至此,文初提的 Proxy、Mybatis 动态代理相关问题已全部答疑

14、抽象类能否 JDK 动态代理

说代码前结论先行,不能!
public abstract class AbstractProxy {
    abstract void sayHello();
}

AbstractProxy proxyInterface = (AbstractProxy) Proxy
        .newProxyInstance(
                ProxyTest.class.getClassLoader(),
                new Class[]{AbstractProxy.class},
                new ProxyInvocationHandler());
proxyInterface.sayHello();

毫无疑问,报错是必然的,JDK 是不能对类进行代理的

带着小疑惑我们看一下 Proxy 源码报错位置,JDK 动态代理在生成代理类的过程代码中,会有是否接口验证

抽象类终归是类,加个 abstract 也成不了接口(就像我,虽然150 斤,但依然是帅哥)

15、什么是缓存

缓存是存在于内存中的临时数据。 使用缓存减少和数据库的交互次数,提高执行效率。

1、适用于缓存 经常查询并且不经常改变的; 数据的正确与否对最终结果影响不大的; 2、不适用于缓存 经常改变的数据; 数据的正确与否对最终结果影响很大的; 例如:商品的库存,银行的汇率,股市的牌价;

16、mybatis一级缓存

1、一级缓存简介 一级缓存作用域是sqlsession级别的,同一个sqlsession中执行相同的sql查询(相同的sql和参数),第一次会去查询数据库并写到缓存中,第二次从一级缓存中取。

一级缓存是基于 PerpetualCache 的 HashMap 本地缓存,默认打开一级缓存。

2、何时清空一级缓存 如果中间sqlSession去执行commit操作(执行插入、更新、删除),则会清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

一级缓存时执行commit,close,增删改等操作,就会清空当前的一级缓存;当对SqlSession执行更新操作(update、delete、insert)后并执行commit时,不仅清空其自身的一级缓存(执行更新操作的效果),也清空二级缓存(执行commit()的效果)。

3、一级缓存无过期时间,只有生命周期 MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个Executor对象,Executor对象中持有一个PerpetualCache对象,见下面代码。当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

17、mybatis二级缓存

1、二级缓存简介 它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。

二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。

2、二级缓存何时存入 在关闭sqlsession后(close),才会把该sqlsession一级缓存中的数据添加到namespace的二级缓存中。

开启了二级缓存后,还需要将要缓存的pojo实现Serializable接口,为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中。

3、二级缓存有过期时间,但没有后台线程进行检测 需要注意的是,并不是key-value的过期时间,而是这个cache的过期时间,是flushInterval,意味着整个清空缓存cache,所以不需要后台线程去定时检测。

每当存取数据的时候,都有检测一下cache的生命时间,默认是1小时,如果这个cache存活了一个小时,那么将整个清空一下。

4、当 Mybatis 调用 Dao 层查询数据库时,先查询二级缓存,二级缓存中无对应数据,再去查询一级缓存,一级缓存中也没有,最后去数据库查找。

18、{}和${}的区别是什么?

  1. {}是预编译处理,${}是字符串替换。

  2. Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;

  3. Mybatis在处理时 , 就 是 把 {}时,就是把时,就是把{}替换成变量的值。

  4. 使用#{}可以有效的防止SQL注入,提高系统安全性。

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

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

20、如何执行批量插入?

使用forEach标签或者使用 sqlSessionTemplate

21、通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?

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

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

22、说说Mybaits的优缺点

优点

① 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。

② 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;

③ 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。

④ 能够与Spring很好的集成;

⑤ 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。

缺点

① SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。

② SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

MyBatis框架适用场合:

(1)MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。

(2)对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。

23、Mybatis是如何进行分页的?

Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,先把数据都查出来,然后再做分页。

可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

分页插件的基本原理是什么?

分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql(SQL拼接limit),根据dialect方言,添加对应的物理分页语句和物理分页参数,用到了技术JDK动态代理,用到了责任链设计模式。

24、简述Mybatis的插件运行原理?

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

25、如何编写一个插件?

编写插件:实现Mybatis的Interceptor接口并复写intercept()方法,然后再给插件编写注解,指定要拦截哪一个接口的哪些方法即可,最后在配置文件中配置你编写的插件。推荐:建议收***ybatis插件原理详解

26、Mybatis拦截器介绍

MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截MyBatis中的哪些内容呢?

我们进入官网看一看:

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) ParameterHandler (getParameterObject, setParameters) ResultSetHandler (handleResultSets, handleOutputParameters) StatementHandler (prepare, parameterize, batch, update, query) 我们看到了可以拦截Executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的一些方法等。

总体概括为:

  1. 拦截执行器的方法

  2. 拦截参数的处理

  3. 拦截结果集的处理

  4. 拦截Sql语法构建的处理

27、拦截器的使用

首先我们看下MyBatis拦截器的接口定义:
public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);

}

比较简单,只有3个方法。 MyBatis默认没有一个拦截器接口的实现类,开发者们可以实现符合自己需求的拦截器。

Plugin方法:是拦截器用于封装目标对象,同过该方法可以返回目标对象本身,也可以返回它的一个代理。当返回代理时,我们可以对其中的方法进行拦截来调用intercept方法,也可以调用其他方法。 SetProperties方法:用于在mybatis配置文件中指定一些属性。 Interceptor方法:是拦截时需要执行的方法(重要实现)。

步骤: 1、创建拦截器类实现实现org.apache.ibatis.plugin.Interceptor接口 2、为自定义拦截器类添加注解org.apache.ibatis.plugin.Intercepts

例如:
@Intercepts({       
        @Signature(      
                type = Executor.class,    
                method = "query",        
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}  
        ),      
        @Signature(     
                type = Executor.class,    
                method = "query",          
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class} 
        ),     
        @Signature(         
                type = Executor.class,                
                method = "update",         
                args = {MappedStatement.class, Object.class}     
        )
}) @Component public class MybatisInterceptor implements Interceptor {   
@Intercepts:标明该类是一个拦截器
@Signature:可以理解为一个拦截项
type:指定拦截的接口类型
method:指定拦截的方法类型

运行被拦截的方法有如下 Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed),对执行器进行拦截,可以对sql执行前后,做一些特殊操作

ParameterHandler (getParameterObject, setParameters),对参数处理器进行拦截,对参数做处理

ResultSetHandler (handleResultSets, handleOutputParameters)对结果集做处理

StatementHandler (prepare, parameterize, batch, update, query)

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

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

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

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

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

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

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

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

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

29.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需要具有很强的经验和能力才行。

总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。

30.使用Mybatis的mapper接口调用时有哪些要求?

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

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

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

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



公众号:"码出宇宙"  后续将持续更新干货八股文,质量文章,新的技术。 喜欢小码哥的话请点赞收藏加关注支持小码哥吧!

本文正在参与【内行知多少】 征文活动,一起来聊聊内行人才懂的那些事吧,高额牛币和百元京东卡等你来领~


#搞技术你要知道##笔试题目##面经##笔经##Java#
小码哥高频面经及八股文 文章被收录于专栏

宝剑锋从磨砺出,梅花香自苦寒来,我是小码哥为你圆梦大厂少走弯路,值得关注。

全部评论
总结的很给力,很详细,帮顶
1 回复 分享
发布于 2022-05-26 14:19
Mybatis 30问 总结的很到位
点赞 回复 分享
发布于 2022-05-26 14:31
点赞 回复 分享
发布于 2022-05-26 14:55
刚死在Mybatis,就看了这篇,有救了,收藏了。
点赞 回复 分享
发布于 2022-05-26 15:08
总结的很到位,自己以前没有学明白的都看明白了
点赞 回复 分享
发布于 2022-05-26 15:56
好厉害
点赞 回复 分享
发布于 2022-05-26 16:05
点赞 回复 分享
发布于 2022-05-26 16:53
点赞 回复 分享
发布于 2022-06-01 10:50
{"pureText":"漂亮"}
点赞 回复 分享
发布于 2022-08-26 12:16 山东

相关推荐

10-14 23:01
已编辑
中国地质大学(武汉) Java
CUG芝士圈:虽然是网上的项目,但最好还是包装一下,然后现在大部分公司都在忙校招,十月底、十一月初会好找一些。最后,boss才沟通100家,别焦虑,我去年暑假找第一段实习的时候沟通了500➕才有面试,校友加油
点赞 评论 收藏
分享
评论
25
114
分享
牛客网
牛客企业服务