Mybatis笔记

Mybatis简介

MyBatis 本是apache的一个开源项目iBatis, MyBatis 是一个高级
映射的优秀的持久层orm框架。
MyBatis 避免了几乎所有的 JDBC 代码和手动设
置参数以及获取结果集。
MyBatis可以使用简单的XML或注解用于配置和原
始映射,将接口和Java的POJO(Plain Old Java
Objects,普通的Java对象)映射成数据库中的记
录.

MyBatis历史

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(sDAO)
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

Mybatis的架构

实现功能
image

MyBatis的实现原理

       

mybatis底层还是采用原生jdbc来对数据库进行操作的,只是通过 SqlSessionFactory,SqlSession Executor,StatementHandler,ParameterHandler,ResultHandler和TypeHandler等几个处理器封装了这些过程

为什么要使用MyBatis?

  • MyBatis是一个半自动化的持久化层框架。
  • JDBC
    – SQL写在Java代码块里,耦合度高导致硬编码内伤
    – 维护不易且实际开发需求中sql是有变化,频繁修改的情况多见
  • Hibernate和JPA
    1. 长难复杂SQL,对于Hibernate而言处理也不容易
    2. 内部自动生产的SQL,不容易做特殊优化。
    3. 基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难。
      导致数据库性能下降。
  • 对开发人员而言,核心sql还是需要自己优化
  • sql和java编码分开,功能边界清晰,一个专注业务、一个专注数据。

    SqlSession

  • SqlSession 的实例不是线程安全的,因此是不能
    被共享的。
  • SqlSession每次使用完成后需要正确关闭,这个
    关闭操作是必须的
  • SqlSession可以直接调用方法的id进行数据库操
    作,但是我们一般还是推荐使用SqlSession获取
    到Dao接口的代理类,执行代理对象的方法,可
    以更安全的进行类型检查操作

缓存机制

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

②、

  • 二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,二级缓存是跨SqlSession的。
  • 二级缓存的原理和一级缓存原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是一级缓存是基于 sqlSession 的,而 二级缓存是基于 mapper文件的namespace的,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中。
  • 开启了二级缓存后,还需要将要缓存的pojo实现Serializable接口,为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中,如果我们要再取这个缓存的话,就需要反序列化了。所以mybatis中的pojo都去实现Serializable接口。  

我们可以看到 mapper.xml 文件中就这么一个空标签,其实这里可以配置,PerpetualCache这个类是mybatis默认实现缓存功能的类。我们不写type就使用mybatis默认的缓存,也可以去实现 Cache 接口来自定义缓存。

image

代码

    @Test
    /** 
     * 返回一行数据  
     * 测试一级缓存
     */
    public void fun() throws IOException {
        // 使用类加载器加载mybatis的配置文件(它也加载关联的映射文件)
        // 1.加载配置文件
        Reader reader = Resources.getResourceAsReader("mybatis.xml");
        // 创建sqlsessionfactory 二级缓存 通过配置信息构建一个sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        // 2.获取链接 一级缓存 通过sqlSessionFactory打开一个数据库会话
        SqlSession session = sqlSessionFactory.openSession();
        // 3.获得mapper
        // 把接口和sql映射文件对应,对应之后就可以通过接口找到具体的sql位置
        ProductDaoMapper mapper = session.getMapper(ProductDaoMapper.class);
        Product pro = mapper.selectOne(1);
        System.out.println(pro.toString());
        // 第二次查询
        ProductDaoMapper mapper2 = session.getMapper(ProductDaoMapper.class);
        Product pro2 = mapper2.selectOne(1);
        System.out.println(pro2.toString());
        session.close();
    }
二级缓存
@Test
public void testTwoCache(){
    //根据 sqlSessionFactory 产生 session
    SqlSession sqlSession1 = sessionFactory.openSession();
    SqlSession sqlSession2 = sessionFactory.openSession();

    String statement = "com.ys.twocache.UserMapper.selectUserByUserId";
    UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
    //第一次查询,发出sql语句,并将查询的结果放入缓存中
    User u1 = userMapper1.selectUserByUserId(1);
    System.out.println(u1);
    sqlSession1.close();//第一次查询完后关闭sqlSession

    //第二次查询,即使sqlSession1已经关闭了,这次查询依然不发出sql语句
    User u2 = userMapper2.selectUserByUserId(1);
    System.out.println(u2);
    sqlSession2.close();
}

二级缓存的应用场景

  对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。

       mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分的,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题可能需要在业务层根据需求对数据有针对性缓存。

二级缓存整合其他缓存框架

       mybatis自带的二级缓存,但是这个缓存是单服务器工作,无法实现分布式缓存。那么什么是分布式缓存呢?假设现在有两个服务器1和2,用户访问的时候访问了1服务器,查询后的缓存就会放在1服务器上,假设现在有个用户访问的是2服务器,那么他在2服务器上就无法获取刚刚那个缓存。

       为了解决这个问题,就得找一个分布式的缓存,专门用来存储缓存数据的,这样不同的服务器要缓存数据都往它那里存,取缓存数据也从它那里取

二级缓存如:Ehcache,Redis

面试题目

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

答:Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement,举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一个

Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。

Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。

缓存

(1)一级缓存(SqlSession级别)

  MyBatis一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行

     相同的sql语句,第一次执行后会将查询到的数据存储到缓存当中,第二次会从缓

     存中进行查找,从而提高查询效率,当一个SqlSession结束之后,一级缓存也将不

     存在,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,Mybatis默认开启一级缓存。

(2)二级缓存(Mapper级别)

     二级缓存的作用域是mapper的同一个namespace,执行两次相同的SQL语句,

     第一次执行后会将查询到的数据存储到缓存当中,第二次会从缓存中进行查找,从而提高查询效率,默认不开启

(3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。

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

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

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

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

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

MyBatis编程步骤是什么样的?

① 创建SqlSessionFactory

② 通过SqlSessionFactory创建SqlSession

③ 通过sqlsession执行数据库操作

④ 调用session.commit()提交事务

⑤ 调用session.close()关闭会话

MyBatis与Hibernate有哪些不同?

(1)Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。

(2)Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。

使用Hibernate查询关联对象或者关联集合对象时,

可以根据对象关系模型直接获取,所以它是全自动的。而Mybatis在查询关联对象或关联集合对象时,

需要手动编写sql来完成,所以,称之为半自动ORM映射工具。