1.说一下对Mybatis的理解
1.1 mybatis概念
mybatis是一个半自动的持久层ORM框架
1.2 什么是ORM
Object Relational Mapping【对象 关系 映射】,将Java中的对象与数据库中表建议映射关系
1.3 Mybatis与Hibernate对比
Mybatis是一个半自动化,需要手写SQL;Hibernate是全自动化,无需手写SQL
1.4 Mybatis与JDBC对比
Mybatis是在JDBC基础之上封装的;JDBC是Java提供的能够直接使用Java代码来编写SQL语句并执行数据库操作的标准API,JDBC需要开发人员在Java代码中编写sql,耦合度高,mybatis可以通过xml方式和Java代码解耦
2. Mybatis的xml映射文件的规则
- xml映射文件的位置:resources/mapper文件夹下
- 映射文件名称要和Java接口名称一致
- 映射文件的namespace与接口的全限定名一致
- 映射文件的标签id和接口方法名称一致
3. 自定义映射
3.1 resultMap和resultType区别
- resultMap和resultType都是用来映射查询结果集的,当数据库字段属性名和Java对应的实体相同时可以直接使用resultType
- 如果当数据库字段属性名和Java对应的实体不相同,例如实体中字段是userName,数据库是user_name,那么就查询出的结果集就和Java实体对应不上,有两种解决办法
- 起别名 例如 user_name as userName
<select id="selectUser" resultType="User">select id,user_name as userName from user where id = #{id}</select>
- 使用resultMap配置映射关系
<resultMap id="userMap" type="com.lby.dto.User"><id column="id" jdbcType="INTEGER" property="id" /><result column="user_name" jdbcType="VARCHAR" property="userName" /></resultMap><select id="selectUser" resultMap="userMap">select id,user_name from user where id = #{id}</select>
- resultMap和resultType只能使用一个
3.2 association和collection 映射
<resultMap id="userAndDeptResultMapAssociation" type="employee"><!-- 定义主键字段与属性关联关系 --><id column="id" property="id"></id><!-- 定义非主键字段与属性关联关系--><result column="user_name" property="userName"></result><!-- 用戶所属部门,自定义关联关系--><association property="dept" javaType="com.lby.mybatis.pojo.Dept"><id column="dept_id" property="deptId"></id><result column="dept_name" property="deptName"></result></association></resultMap><resultMap id="userAndEmpResultMap" type="dept"><id property="deptId" column="dept_id"></id><result property="deptName" column="dept_name"></result><collection property="empList" ofType="com.lby.mybatis.pojo.Employee"><id column="id" property="id"></id><result column="last_name" property="lastName"></result><result column="email" property="email"></result><result column="salary" property="salary"></result></collection></resultMap>
- association标签:定义一对一或多对一关联关系
- colunm:设置SQL中参数
- property:SQL关联实体的属性
- javaType:关联实体属性的类型
- select:设置分步查询SQL全路径
- fetchType:设置局部延迟加载【懒加载】是否开启
- collection标签:定义一对多的关联关系
- property:SQL关联实体的属性
- ofType:关联实体属性的类型
- fetchType:设置局部延迟加载【懒加载】是否开启
4. Mybatis插入数据的时候,怎样获取插入数据后的主键id
- useGeneratedKeys:启用主键生成策略,添加成功后可以返回主键值,并存储到keyProperty
- keyProperty:设置存储属性值,对应的是Java实体中的属性
<insert id="addUser" useGeneratedKeys="true" keyProperty="id" parameterType="User">insert into user (id,user_name) values (#{id},#{userName})</insert>
5. mybatis中的参数是如何传递的
5.1 自定义个数参数
@Param可以将接口的参数和xml中#{}的参数进行映射
public List<Employee> selectEmpByNamed(@Param("lName")String lastName, @Param("salary") double salary);
5.2 复杂类型类类型Bean传参
Integer updateByPrimaryKey(UserDTO dto);
<update id="updateByPrimaryKey" parameterType="UserDTO" >update user<set ><if test="userName != null" >user_name = #{userName,jdbcType=VARCHAR},</if></set>where id = #{id,jdbcType=BIGINT}</update>
5.3 map参数
<select id="queryUserList" resultMap="UserMap" parameterType="java.util.Map">select<include refid="Base_Column_List" />from user uwhere 1=1<if test="userName != null">and u.user_name = #{userName,jdbcType=VARCHAR}</if>ORDER BY u.id</select>
5.4集合参数传递
public List<User> selectUserList(@Param("ids") List<Integer> ids);
<select id="selectUserByIds" resultType="User">SELECT `id`, user_name FROM `tbl_employee`<where>`id` in (<foreach collection="ids" item="id" separator=",">#{id}</foreach>)</where></select>
6. 参数传递#和$区别
- 【#】底层执行SQL语句的对象,使用PreparedStatementd,预编译SQL,防止SQL注入安全隐患,相对比较安全。
- 【$】底层执行SQL语句的对象使用Statement对象,未解决SQL注入安全隐患,相对不安全。
7. mybatis查询返回值的集中情况
7.1 返回单个对象
public User selectUserById(int id);
<select id="selectUserById" resultType="user">SELECT id, user_name as userName FROM user WHERE id= #{id}</select>
7.2 返回对象集合
public List<User> selectAllUser();
<select id="selectAllUser" resultType="user">SELECT id, user_name as userName FROM user</select>
如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。
7.3 返回Map集合
- 对象的id作为key
- 对象作为value
@MapKey("id")public Map<Integer,User> selectUserReturnMap();
<select id="selectUserReturnMap" resultType="map">SELECT id, user_name as userName FROM user</select>
8. mybatis的分步查询
- 为什么要分步查询?
多表关联查询时,可能会导致性能低下,通过分步查询,可以减少表连接的次数 - 如何进行分布查询?
在一对多的association情况下,可以借助select属性,引用查询,具体实现如下
- java代码
@Data
public class User {private int id;private String username;private List<Order> orders;
}
@Data
public class Order {private int id;private int userId;private String product;
}
- Mapper接口
import java.util.List;
public interface UserMapper {User getUserById(int userId);
}
- XML映射文件
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper"><resultMap id="userResultMap" type="User"><id property="id" column="id"/><result property="username" column="username"/><!-- 使用association的select属性实现分步查询 --><association property="orders" javaType="List" select="getOrdersByUserId"/></resultMap><select id="getUserById" resultMap="userResultMap" parameterType="int">SELECT * FROM users WHERE id = #{userId}</select><select id="getOrdersByUserId" resultMap="orderResultMap" parameterType="int">SELECT * FROM orders WHERE user_id = #{userId}</select></mapper>
9. Mybatis延迟加载
- 什么是延迟加载
需要时加载,不需要不加载 - 有什么好处
只在必要的时候才查询,提高性能 - 如何实现延迟加载
- 全局配置
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 设置加载的数据是按需加载3.4.2及以后的版本该步骤可省略-->
<setting name="aggressiveLazyLoading" value="false"/>
- 局部配置
使用fetchType属性:
eager:关闭局部延迟加载
lazy:开启局部延迟加载
<association property="dept"select="com.atguigu.mybatis.mapper.DeptMapper.selectDeptByDeptId"column="dept_id"fetchType="eager">
</association>
10. 动态sql
- if标签:用于完成简单的判断
- where标签:用于解决where关键字及where后第一个and或or的问题
- trim标签: 可以在条件判断完的SQL语句前后添加或者去掉指定的字符
- prefix: 添加前缀
- prefixOverrides: 去掉前缀
- suffix: 添加后缀
- suffixOverrides: 去掉后缀
- set标签:主要用于解决set关键字及多出一个【,】问题
- choose标签:类似java中if-else【switch-case】结构
- foreach标签:类似java中for循环
- collection: 要迭代的集合
- item: 当前从集合中迭代出的元素
- separator: 元素与元素之间的分隔符
- open: 开始字符
- close:结束字符
- sql标签:提取可重用SQL片段
<sql id="emp_col">id,last_name,email,salary</sql><sql id="select_employee">selectid,last_name,email,salaryfromtbl_employee</sql><!-- 按条件查询员工信息【条件不确定】--><select id="selectEmpByOpr" resultType="employee"><include refid="select_employee"></include><where><if test="id != null">and id = #{id}</if><if test="lastName != null">and last_name = #{lastName}</if><if test="email != null">and email = #{email}</if><if test="salary != null">and salary = #{salary}</if></where></select><select id="selectEmpByOprTrim" resultType="employee"><include refid="select_employee"></include><trim prefix="where" suffixOverrides="and"><if test="id != null">id = #{id} and</if><if test="lastName != null">last_name = #{lastName} and</if><if test="email != null">email = #{email} and</if><if test="salary != null">salary = #{salary}</if></trim></select><update id="updateEmpByOpr">updatetbl_employee<set><if test="lastName != null">last_name=#{lastName},</if><if test="email != null">email=#{email},</if><if test="salary != null">salary=#{salary}</if></set>whereid = #{id}</update><select id="selectEmpByOneOpr" resultType="employee">select<include refid="emp_col"></include>fromtbl_employee<where><choose><when test="id != null">id = #{id}</when><when test="lastName != null">last_name = #{lastName}</when><when test="email != null">email = #{email}</when><when test="salary != null">salary = #{salary}</when><otherwise>1=1</otherwise></choose></where></select><select id="selectEmpByIds" resultType="employee">selectid,last_name,email,salaryfromtbl_employee<where>id in(<foreach collection="ids" item="id" separator=",">#{id}</foreach>)</where></select><insert id="batchInsertEmp">INSERT INTOtbl_employee(last_name,email,salary)VALUES<foreach collection="employees" item="emp" separator=",">(#{emp.lastName},#{emp.email},#{emp.salary})</foreach></insert>
11. mybatis缓存机制
mybaits有二级缓存
11.1 一级缓存
- 基本概念
一级缓存的模式有两种,默认是sqlSession级别对当前回话有效,另一种是statement是sql级别,sqlSession级别缓存在分布式环境下,一个sqlSession更新了数据,其他缓存结果的sqlSession是看不到更新后数据的,所以建议将缓存级别设定为Statetment - sqlSession缓存的原理
第一次获取数据,先从数据库加载数据,将数据缓存到Mybatis一级缓存中,一级缓存使用的是map,其中key是 hashCode + 查询sql的id + 编写sql的查询语句 + 参数构成;再次获取数据时,先从一级缓存获取,未获取到再成数据库中获取 - 一级缓存的失效情况
- sql相同,但是sqlSession不同
- 相同sqlSession中,两次相同sql查询中间执行了任何的增删改操作(这个操作会导致清空缓存)
- 相同sqlSession中的两次相同搞的sql查询中间提交了事务
11.2 二级缓存
- 概念
二级缓存是SqlSessionFactory级别的缓存,二级缓存默认关闭,二级缓存需要提交sqlSession或者关闭sqlSession才会缓存。 - 二级缓存如何使用
- 全局配置文件开启二级缓存
<setting name="cacheEnabled" value="true"/>
- 开始二级缓存对应的实体PO需要实现Serializable接口
- 在对应需要序列化的xml映射文件标签中使用
- 二级缓存原理
第一次查询先从数据库获取数据,让后将书记缓存到一级缓存,当SqlSession提交或者关闭后,将数据缓存到二级缓存;再次获取数据时先从一级缓存获取,一级缓存没有数据再从二级缓存获取,二级缓存也没有从数据库获取 - 二级缓存失效情况
两次查询中见,执行增删改操作,一级二级缓存都会被清空
12. Mybatis字段映射原理
- 通过JDBC API向数据库发送SQL查询语句,获取查询结果集
- 结果集每一行的数据都封装成ResultSet对象
- 每一行数据,mybatis都会根据Java对象的属性和结果集的列名称匹配,匹配成功,则将结果映射到对应属性中
- 对于实体属性和对应表的列不一致,可以在sql中使用AS,或者通过resultMap定义映射关系
- 最终mybatis会将所有的映射成功的Java对象封装成一个List集合返回
13. 获取SqlSession的流程是怎样的
- SqlSessionFactoryBuilder解析配置文件,生成一个Configration对象,这个对象中包含了MyBatis需要的所有配置,然后会用这个Configration对象创建一个SqlSessionFactory对象,这个对象中包含了Configration对象。
- 调用SqlSessionFactory的openSesison方法,这个方法会创建一个Sql执行器(Executor组件中包含了Transaction对象),这个Sql执行器会代理你配置的拦截器方法。
- 获得上面的Sql执行器后,会创建一个SqlSession(默认使用DefaultSqlSession),这个SqlSession中也包含了Configration对象和上面创建的Executor对象,所以通过SqlSession也能拿到全局配置;获得SqlSession对象后就能执行各种CRUD方法了。
14. mysql的Sql是怎样执行的
- 调用SqlSession的getMapper方法,获得Mapper接口的动态代理对象MapperProxy
- 调用Mapper接口的所有方法都会调用MapperProxy的invoke方法(动态代理机制)创建MapperMethod对象
- 调用MapperMethod的execute方法,sqlSession会作为execute方法的入参
- 进入Executor组件的query方法,这个方法中会创建一个StatementHandler对象,这个对象中同时会封装ParameterHandler和ResultSetHandler对象。
- 使用ParameterHandler来给sql设置参数。
- 使用StatementHandler的增删改查方法获得结果,ResultSetHandler对结果进行封装转换,请求结束。
Executor、StatementHandler 、ParameterHandler、ResultSetHandler,Mybatis的插件会对上面的四个组件进行动态代理。