学到郁郁寡欢之 - MyBatis 基础

先腐一下

1 MyBatis 是啥

MyBatis 是一款优秀的基于 ORM(Object Relation Mapping) 的半自动轻量级持久层框架,它支持定制化 SQL,存储过程以及高级映射。MyBatis 底层封装了 JDBC,开发人员避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 xml 或注解来配置和映射原生类型,接口和 Java 的 pojo 为数据库中的记录。

不同于 Hibernate:Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而 Mybatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 映射工具。

2 MyBatis 的优缺点

2.1 优势

MyBatis 是一个半自动的持久层框架。对开发人员而言,核心 sql 还是需要自己优化,sql 和 java 编码分开,功能边界清晰,一个专注业务,一个专注数据。

2.2 缺点

  • SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写 SQL 语句的功底有一定要求;
  • JDBC 方式可以用打断点的方式调试,但是 MyBatis 不能,需要通过 Log4j 日志信息帮助调试,然后在配置文件中修改。

3 适用场合

  • 对性能的要求很高的项目,做 SQL 的性能优化相对简单、直接;
  • 需求变化较多的项目,直接编写或修改 SQL。

4 配置文件相关问题

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

我觉得要尽量避免这种情况

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

<select id=”selectOrder” parametertype=”int” resultetype=”com.order”>
select order_id id, order_no orderno ,order_price price form
orders where order_id=#{id};
</select>
(2)第 2 种: 通过<resultMap> 来映射字段名和实体类属性名的一一对应的关系。
<resultMap id="userMap" type="com.User">
    <!–用 result 属性来映射非主键字段,property 为实体类属性名,column 为数据表中的属性–>
    <result column="id" property="id"></result>
    <result column="username" property="username"></result> 
    <collection property="orderList" ofType="com.Order"> 
        <result column="oid" property="id"></result> 
        <result column="ordertime" property="ordertime"></result> 
        <result column="total" property="total"></result>
    </collection>
</resultMap> 

<select id="findAll" resultMap="userMap">
     select *,o.id oid from user u left join orders o on u.id=o.uid
</select>

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

Dao 接口即 Mapper 接口。接口的全限定名对应于映射文件中 namespace 的值,接口的方法名对应于映射文件中每条 sql 的 id 值;接口方法内的参数,就是传递给 sql 的参数值。

采用 Mapper 代理开发方式时,Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个 MapperStatement。在 Mybatis 中,每一个 <select>、<insert>、<update>、<delete>标签,都会被解析为一个 MapperStatement 对象。
Mapper 接口里的方法,是不能重载的,因为是使用 全限定名+方法名 的保存和寻找策略。Mapper 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK 动态代理为 Mapper 接口生成代理对象 proxy,代理对象会拦截接口方法,最终调用 SqlSession 提供操作 DB 的方法,然后将 sql 执行结果返回。

(1)传统开发方式

public interface UserDao {
    List<User> findAll() throws IOException; 
}

public class UserDaoImpl implements IUserDao {

    public void delete() throws IOException {
        InputStream resourceAsStream =
        Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new
        SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 命名空间.id
        sqlSession.selectList("userDao.delete");
        sqlSession.close();
        return userList;
    }
}
<mapper namespace="userDao"> 
    <delete id="delete" parameterType="java.lang.Integer">
         delete from user where id=#{id}
    </delete>
</mapper>

(2)代理开发方式,将由 Mybatis 框架根据接口定义创建接口的动态代理对象

  • mapper.xml 文件中的 namespace 与 mapper 接口的全限定名相同
  • Mapper 接口方法名和 mapper.xml 文件中的每个 statement 的 id 相同
  • Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同
  • Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同
public interface IUserDao {

    //查询所有用户
    public List<User> findAll() throws IOException;

}
<mapper namespace="com.lagou.dao.IUserDao">
    <!--namespace: 名称空间:与id组成sql的唯一标识
        resultType:表明返回值类型-->

    <!--查询用户-->
    <select id="findAll" resultType="user">
       <include refid="selectUser"></include>
    </select>
</mapper>
    @Test
    public void test6() throws IOException {

        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获取动态代理对象
        IUserDao mapper = sqlSession.getMapper(IUserDao.class);
        List<User> all = mapper.findAll();
    }

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

(1)使用<resultMap>标签,逐一定义数据库列名和对象属性名之间的映射关系。

<resultMap id="ApprovalInfoResult" type="ApprovalInfoBean">
    
    <result property="id" column="id"/>
    
    <result property="userId" column="user_id"/>
    
</resultMap>

<select id="getList" resultMap="ApprovalInfoResult" parameterType="java.util.Map">
</select>

(2)使用 sql 列的别名功能,将列的别名书写为对象属性名。

public class ApprovalBookOnlineInfoDTO {

    
    /** 版权书籍ID */
    
    private Long copyrightBookId;
}
<select id="getApprovalOnlineInfoDTOList" resultType="ApprovalBookOnlineInfoDTO" parameterType="java.util.Map">
    
    SELECT
  id AS copyrightBookId from a_approval;
</select>

4.4 自动生成主键

<insert id="insert" parameterType="ApprovalInfoBean">
    
    <selectKey resultType="java.lang.Long" keyProperty="id" order="AFTER">
                
       SELECT LAST_INSERT_ID()
    
    </selectKey>
    
    insert into a_approval_info(id,user_id)
 values(#{id},#{userId}
</insert>

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

(1)动态sql:进行sql操作时,动态的根据属性值来拼接数据库执行的sql语句,也就是根据传入的值不同,动态拼接出不同的可执行sql。包含判断为空、循环等;

(2)动态 sql 标签

  •  标签 if - 用于判断传入的值是否符合某种条件;
  • where - 用来动态拼接查询条件,当和if标签配合的时候,不用显示的声明类似where 1=1;
  • foreach - 用于构建 in 条件,可在 sql 中对集合进行迭代。也常用到批量删除、添加等操作;
  • choose when otherwise - 按顺序判断 when 中的条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的 sql。类似于 Java 的 switch 语句,choose 为 switch,when 为 case,otherwise 则为 default;
  • include - 提取重复的sql,达到 sql 重用的目的。

(3)动态 sql 的执行原理

① 解析 Mapper 配置文件 mapper.xml,文件中的一个 select/update/delete/insert 节点对应于一个 MappedStatement 对象,然后存储在 Configuration 对象的 mappedStatements 属性中,mappedStatements 是一个 HashMap,存储时 key = 全限定类名+方法名,value = 对应的MappedStatement 对象;

② 获取用于和数据库交互的 SqlSession,传递的参数为 Configuration 对象和 Executor 执行器,然后根据传入的全限定名和方法名从 map 中获取 MappedStatement 对象;

③ 执行 executor.query(),根据传入的参数动态获得 SQL 语句,最后用 BoundSql 对象表示;

④ 创建 statementHanlder 对象执行查询,为 sql 中的占位符设值,最终返回 List 结果集。

4.6 Mybatis 的 Xml 映射文件中,不同的 Xml 映射文件,id 是否可以重复?

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

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

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

MyBatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在 MyBatis配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。它的原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB() 是 null 值,那么就会单独执行事先保存好的查询关联 B 对象的 sql,把 B 查询出来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。这就是延迟加载的基本原理。

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

(1)SimpleExecutor:默认的普通执行器

(2)BatchExecutor:重用语句并执行批量更新

(3)ReuseExecutor:重用预处理语句 prepared statements

4.9 简述下Mybatis的一级、二级缓存(分别从存储结构、范围、失效场景。三个方面来作答)?

(1)一级缓存:一级缓存是 SqlSession 级别的缓存。在操作数据库时需要构造 SqlSession 对象,SqlSession 用一个 HashMap 来存储缓存数据。不同 SqlSession 之间的缓存数据区域(HashMap)是互相不影响的。一级缓存默认开启。

(2)二级缓存:二级缓存是 mapper 级别的缓存,多个 SqlSession 去操作同一个 Mapper 的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。二级缓存需要手动开启。二级缓存中的pojo需要实现 Serializable 接口,因为二级缓存数据存储介质多种多样,再去取pojo 时就需要反序列化了。

4.10 简述Mybatis的插件运行原理,以及如何编写一个插件?

(1)MyBatis 插件的运行原理 MyBatis 的插件实质上是拦截器,用于增强 MyBatis 的四大核心对象。增强功能本质上是借助底层的动态代理实现的。使用插件为四大对象创建出代理对象,拦截四大对象的每一个执行。interceptorChain保存了所有的拦截器(interceptors)。依次调用拦截器链中的拦截器依次对目标进行拦截或增加。

(2)如何编写一个插件 ① 创建一个插件类实现 interceptor 接口,使用注解定义要拦截哪个接口,接口里的哪个方法; ② 重写拦截方法,对原有方法进行增强 ③ 为自定义拦截器生成代理对象,放入拦截器链中 ④ 在sqlMapConfig.xml 配置自定义插件的全限定名

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值