一、概述
MyBatis是一款优秀的开源持久层框架,支持定制SQL,存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和结果集映射的封装,大大解放了生产力,今天我们来看它的简单实现以及常见的面试题。
二、基础回顾
首先我们来回顾一下MyBatis的基本用法,创建工程,引入坐标,如下图
添加jdbc.properties配置文件,输入数据库的连接信息,添加sqlMapConfig.xml,该配置文件用来添加MyBatis的别名映射包、数据库、以及mapper映射文件等信息,如下图
sqlMapConfig.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文件-->
<properties resource="jdbc.properties"></properties>
<!--给实体类的全限定类名给别名-->
<typeAliases>
<!--给单独的实体起别名-->
<!-- <typeAlias type="com.lagou.pojo.User" alias="user"></typeAlias>-->
<!--批量起别名:该包下所有的类的本身的类名:别名还不区分大小写-->
<package name="com.gzh.mybatis.pojo"/>
</typeAliases>
<!--environments:运行环境-->
<environments default="development">
<environment id="development">
<!--当前事务交由JDBC进行管理-->
<transactionManager type="JDBC"></transactionManager>
<!--当前使用mybatis提供的连接池-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--引入映射配置文件-->
<mappers>
<mapper resource="UserMapper.xml"></mapper>
</mappers>
</configuration>
然后添加实体类GzhUser、UserInfo、WorkExperience
再添加GzhUserDao接口,内容如下
添加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="com.gzh.mybatis.dao.GzhUserDao">
<!--namespace : 名称空间:与id组成sql的唯一标识
resultType:表明返回值类型-->
<resultMap id="user" type="com.gzh.mybatis.pojo.GzhUser">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="phone" property="phone"/>
<association property="userInfo" javaType="com.gzh.mybatis.pojo.UserInfo">
<id column="i_id" property="id"/>
<result column="user_id" property="userId"/>
<result column="age" property="age"/>
<result column="gender" property="gender"/>
<result column="address" property="address"/>
</association>
<collection property="workExperiences" ofType="com.gzh.mybatis.pojo.WorkExperience">
<id column="w_id" property="id"/>
<result column="user_id" property="userId"/>
<result column="company_name" property="companyName"/>
<result column="salary" property="salary"/>
</collection>
</resultMap>
<!--抽取sql片段-->
<sql id="selectUser">
select * from gzh_user
</sql>
<sql id="fields">
a.id,a.username,a.password,a.phone,b.age,b.gender,b.address,c.company_name,c.salary
</sql>
<!--查询用户-->
<select id="findAll" resultType="gzhUser">
<include refid="selectUser"/>
</select>
<!--多条件组合查询:演示if-->
<select id="findByUsername" resultType="gzhUser">
<include refid="selectUser"/>
<where>
<if test="username !=null">
username = #{username}
</if>
</where>
</select>
<!--多值查询:演示foreach-->
<select id="findByIds" parameterType="list" resultType="gzhUser">
<include refid="selectUser"/>
<where>
<foreach collection="array" open="id in (" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
<!--更新演示-->
<update id="updatePwdById">
update gzh_user set password = #{pwd}
<where>
id = #{id}
</where>
</update>
<!--一对一查询演示-->
<select id="findUserInfoByUsername" resultMap="user">
<include refid="selectUser"/>
a left join gzh_user_info b on a.id = b.user_id
<where>
<if test="username !=null">
username = #{username}
</if>
</where>
</select>
<!--一对多查询演示-->
<select id="findUserInfoAndWorksById" resultMap="user">
select
<include refid="fields"/>
from gzh_user a left join gzh_user_info b on a.id = b.user_id left join work_experience c on a.id=c.user_id
<where>
<if test="id !=null">
a.id = #{id}
</if>
</where>
</select>
</mapper>
最后添加测试类UserTest,内容如下:
public class UserTest {
@Test
public void test1() throws IOException {
// 获取配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession(); //事务默认不提交
GzhUserDao userDao = sqlSession.getMapper(GzhUserDao.class);
List<GzhUser> users = userDao.findAll();
users.forEach(item -> {
System.out.println(item.toString());
});
System.out.println("=======findAll测试-END=======");
GzhUser user = userDao.findByUsername("lisi");
System.out.println(user.toString());
System.out.println("=======findByUsername测试-END=======");
List<GzhUser> userDaoByIds = userDao.findByIds(new Integer[]{1});
userDaoByIds.forEach(item -> {
System.out.println(item.toString());
});
System.out.println("=======userDaoByIds测试-END=======");
boolean b = userDao.updatePwdById("123456", 1);
sqlSession.commit();
System.out.println(b);
System.out.println("=======updatePwdById测试-END=======");
GzhUser lisi = userDao.findUserInfoByUsername("lisi");
System.out.println(lisi);
System.out.println("=======一对一 测试-END=======");
GzhUser userInfoAndWorksById = userDao.findUserInfoAndWorksById(1);
System.out.println(userInfoAndWorksById);
System.out.println("=======一对一和一对多 测试-END=======");
}
}
test文件执行结果如下:
至此,MyBatis的基础已经演示完毕,主要介绍了单表查询,一对一查询(关键标签:association),一对多查询(关键标签:collection)。
代码连接:https://gitee.com/GPF1217/java-from-xiaobai-to-architect.git
三、精选面试题
-
Mybatis的mapper接口里方法名相同参数不同时支持方法的重载吗?
不支持,因为Mybatis是通过接口的全限定类名+方法名作为唯一的key找到对应的sql,若是方法名相同,则key就不唯一了。 -
Mybatis中#{}和${}有什么区别?
#{}是预编译处理,Mybatis在处理#{}时会将该符号替换为?,调用PreparedStatement的set方法来赋值;${}是字符串替换,Mybatis处理时会直接将该字符号替换为变量值。
使用#{}可以防止sql注入攻击。
-
Mybatis有那些动态SQL,他们有什么用?
Mybatis的动态sql主要通过标签来完成的,常见的动态sql标签有:where、if、choose、otherwise、foreach等等。
Mybatis通过不同的标签,动态的拼接不同的sql,这样可以大大提高sql的复用率,减少代码的冗余。
-
Mybatis缓存机制了解吗?能简单说说吗?
MyBatis分为一级缓存和二级缓存,一级缓存默认开启,二级缓存默认不开启。
一级缓存是SqlSession级别,二级缓存是Mapper级别,两者当数据进行增删改操作时缓存会被删除。
MyBatis二级缓存是单机情况下工作的,如果想要在集群环境下工作,需要将二级缓存放入分布式缓存中。
-
Mybatis插件机制主要对那些组件进行了拦截?
Executor:执行器
StatementHandler:SQL语法构建器
ParameterHandler:参数处理器
ResultSetHandler:结果集映射处理器
主要对这四大核心组件进行额拦截
-
如何自定义Mybatis插件?
Mybatis可以对Executor、StatementHandler、ParameterHandler、ResultSetHandler进行拦截,利用动态代理做方法的增强,其插件原理就是基于此。
自定义插件主要是实现Intercept接口,用注解标记需要拦截的组件,然后重写里面的intercept()方法,编写代码逻辑
-
Mybatis的延迟加载实现原理是什么?
Mybatis仅支持association和collection关联对象的延迟加载。
它的原理是:使用动态代理创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,拦截器方法发现关联对象的属性值为null,就会单独查询关联对象的sql,把关联对象的属性查出来,然后再通过反射赋值,这就是延迟加载的原理
-
Mybatis的执行原理是什么?
Mybatis会将mapper映射文件中的sql封装成MappedStatement对象存储在内存中,根据每条sql的id为其生成动态代理对象,当用户通过接口调用方法时,Mybatis会根据接口的全限定类名+方法名,找到对应的动态代理对象,然后根据传入的参数拼接好对应的sql,通过代理对象执行该sql,得到返回值之后,根据配置的resultType反射成具体的对象,最后返回结果集。
感兴趣的读者朋友可以 关注本公众号,和我们一起学习探究。
本人因所学有限,如有错误之处,望请各位指正!