Mybatis

目录

一、初识Mybatis

(一)Mybatis的原理

(二)Mybatis的配置

二、基于SqlSession的单表增删改查

(一)无参数的

(二)有参数的

(三)SqlSession总结

三、基于Sql动态代理的单表增删改查

四、MybatisSql语句的动态拼接

五、Automapping自动注入和自定义注入

(一)Automapping自动注入

(二)自定义注入

六、动态代理的多表联合查询

(一)业务装配方式(N+1方式)

(二)ResultMap嵌套查询(N+1方式)

(三)ResultMap嵌套结果

七、Mybatis的注解

八、Mybatis的缓存

(一)一级缓存

(二)二级缓存

(三)清空缓存

1.flush()对缓存的影响

3.close()对缓存的影响

3.commit()对缓存的影响

九、基于ThreadLocal的封装

(一)ThreadLocal

(三)基于ThreadLocal的封装

十、责任链设计模式


一、初识Mybatis

(一)Mybatis的原理

在MyBatis开始运行时,先通过Resources加载全局配置文件,然后实例化SqlSessionFactoryBuilder构建器,帮助SqlSessionFactory接口实现类DefaultSqlSessionFactory。SqlSessionFactoryBuilder构建器调用build方法,在build方法中创建XmlConfigBuilder(解析器对象)解析配置文件流(也就是我们传入的InputStream is),并把解析结果存放在Configuration-(中的每一个标签都对应一个configuration成员属性)中,之后通过DefaultSqlSessionFactory的构造器将Configuratin传递给DefaultSqlSessionFactory,到此SqlSessionFactory工厂创建成功。由SqlSessionFactory工厂创建SqlSession,先创建TransactionFactory(实例化Transaction对象),再创建Excutor(实例化对象BaseExcutor,这里面存储了Cache缓存对象),最后实例化DefaultSqlSession,传递给SqlSession接口。

    //作用:获取资源配置文件的流对象,便于对资源文件的读取和解析
    InputStream is = Resuorces.getResourceAsStream("mybatis.xml");

    /**
     *    作用:快速创建DefaultSqlSessionFactory对象,用来生产SqlSession对象
     *    内部操作:
     *        XMLConfigBuilder:用来解析XML配置文件的工具类
     *        Configuration:存储XML解析结果的实体类
     *        DefaultSqlSessionFactory:用来生产SqlSession对象的工厂类
     */
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);

    /**
     *    作用:生产SqlSession对象,用来完成对数据库的操作
     *    内部操作:
     *         Environment:存储了数据库环境信息(来自Configuration对象)
     *         TransactionFactory:用来生产事务管理对象(来自Configuration对象)
     *         executor:执行器,存储了事务管理对象和数据库操作的模式SIMPLE
     *         DefaultSqlSession:用来操作数据库的对象
     */
    SqlSession ss = factory.openSession();

(二)Mybatis的配置




    
    
        
    
    
    
        
        
        
        
    
    
    
        
        
            
            
            
            
                
                
                
                
            
        
    
    
    
        
    

二、基于SqlSession的单表增删改查

(一)无参数的

(1)mapper.xml文件的配置




    
        
        
        
        
            insert into flower values(default,'随便花',199,'bjsxt')
        
        
        
            update flower set name='随便花' where id = 6
        
        
        
            dalete from flower where id = 6
        

(2)在java中的应用

public class Teast{
    public void main(String[] args) throws IOException{
        //1.获取SqlSession对象
        InputStream is = Resources.getResourceAsStream("mybatis.xml");
        SqlSessionFactory factory = new SqlSessionBuilder().build(is);
        SqlSession ss = factory.openSession();
        //2.使用SqlSession对象完成数据库操作
        //a.查询
        List f = ss.selectList("com.bjsxt.mapper.FlowerMapper.selAll");
        //b.增加
        int i = ss.insert("com.bjsxt.mapper.FlowerMapper.insF");
        //c.修改
        int i2 = ss.update("com.bjsxt.mapper.FlowerMapper.upF");
        //d.删除
        int i3 = ss.delete("com.bjsxt.mapper.FlowerMapper.delF");   
    }
}

(二)有参数的

(1)mapper.xml文件的配置




    
    
    
    
     
    
        insert into flower values(default,#{name},#{price},#{producation})
    

(2)在java中的应用

public class Test{
    public void main(String[] args) throws IOException{
        //1.获取SqlSession对象
        InputStream is = Resources.getResourceAsStream("mybatis.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession ss = factory.openSession();
        //2.使用SqlSession对象完成数据库的操作
        //a.一个参数
        Flower f = ss.selectOne("com.bjsxt.mapperFlowerMapper.selF",2);
        //b.多个参数
        Flower f1 = new Flower();
        fp.setId(1);
        fp.setNmae("别的花");
        Flower f2 = ss.selectOne("com.bjsxt.mapper.FlowerMapper.selF2",f1);
        //c.
        Map map = new HashMap();
        map.put("name","彼岸花");
        map.put("price","99.99");
        map.put("production","彼岸");
        int i = ss.insert("com.bjsxt.mapper.FlowerMapper.insF2",map)
    }
}

(三)SqlSession总结

(1)因为SqlSession提供的数据库操作方法声明的实参类型为object,需要通过该属性将数据强制转换为对应的类型。在查询标签上使用parameterType属性声明参数类型

(2)在查询标签上使用parameterType属性声明参数类型

(3)在SQL语句中使用#{0}(基本类型的数据都可以使用角标来占位)、#{属性名|健名}进行占位

(4)第一个参数为要执行的Sql语句全限定路径第二个参数为接收的实参

(5)因为SqlSession对象提供的数据库操作方法只接收一个实参,所以,如果Sql语句中需要使用多个参数,我们必须将参数封装成一个对象,将该对象传递给方法完成数据库操作

三、基于Sql动态代理的单表增删改查

(1)mapper.xml文件的配置




    
    
    
    
    
    
    
    
    
    
    
    
    
    

(2)在java中的应用

public interface FlowerMapper{
    //注意:形参名和xml中的键没有任何关系!只能用@Param()建立映射联系
    //无参数
    List selF();
    //单参数
    Flower selById(int id);
    Flower selByIdName(Flower f);
    //多参数
    Flower selByIdName2(int id,String name);
    Flower selByIdName3(int id,Flower f);
    //注解方式
    Flower selByIdName4(@Param("id")int id,@Param("flower")Flower f);

}

四、MybatisSql语句的动态拼接

(1)标签test中可以直接书写逻辑判断,但是连接符是and或者or,可以使用param1|键名|属性名|注解别名获取参数的值。

(2)标签:如果被包含的内容有任一个成立,则自动生成where关键字,自动去除被包含的内容的第一个and或者or关键字,where中的内容可以是其他的标签,最常用的是if标签和choose标签。(有了这个where,我们在拼接sql语句的时候不用再写where 1 = 1)

(3)标签:判断参数的值,拼接不同的Sql语句。类似java中的多分支语句。此结构最多只能成立一个条件,只要一个成立,其他的就不会再判断执行了。(相当于if(){}if else(){}if else(){}else{}结构)

(4)标签生成一个set关键字去除内容的最后一个逗号,在update语句中使用,实现动态更新。


    update flower 
         
            id=#{param3},
            
                name=#{param1},
            
            
                production=#{param2}
            
         
    where id=#{param3}

(5)标签给指定的内容添加前缀或者去除前缀等。属性:prefix给原有内容添加指定的前缀,prefixOverri-des去除原有内容的指定前缀,suffix给原有内容添加指定后缀,suffixOverrides去除原有内容的后缀。


    update flower
        
            id=#{param3},
            
                name=#{param1},
            
            
                production=#{param2},
            
        
    where id=#{param3}

(6)标签:结合in关键字,实现in语句中的数据的动态拼接。属性:collection:list|array声明遍历的是list集合还是数组,list和array为底层map集合的键名item当次遍历的数据,open遍历所有的结果内容之前要拼接的符号,close遍历所有的结果内容之后拼接的符号,separator内容的间隔符号。

(7)标签给接收的实参重新拼接赋值,一般like语句中使用。属性:name新的键名,value实参。注意:实参的获取底层会调用get方法获取,需要我们使用注解别名,或者对象封装

(8).标签降低sql语句的冗余,使sql语句可以重复调用使用,使用include标签在select子句中引入公共SQL语句声明。



    price, production

五、Automapping自动注入和自定义注入

(一)Automapping自动注入

Mybatis默认按照字段名属性名一致的规则将查询的数据注入到实体类的对象中,这种方式叫Automapping自动注入。如果实体类的属性名和字段名不一致,那么就需要我们使用自定义注入

(二)自定义注入

自定义注入提前声明查询结果和实体类之间的注入规则,就是告诉Mybatis将哪个字段值赋给实体类的哪个属性。一般用于联合查询|单表查询但是字段名和属性名不一致的时候。在查询标签上使用ResultMap属性声明要引入的规则

注意:如果是单表查询的自定义注入可以只声明字段名和属性名不同的注入。但是如果是多表联合查询,不管字段名和属性名是否一致都要全部自定义注入。






    
    
    
    
    
        
        
    

六、动态代理的多表联合查询

(一)业务装配方式(N+1方式)

概念:很多时候,查询需要的结果分布在多张表中。以前我们使用联合查询语句一次性将数据查询出来。但是,其实多表联合查询可以分开成多个单表查询,比如查询学生及其教师,可以分为两个步骤:①查询所有的学生②根据查询的学生的教师编号查询教师信息。我们将以上的思路实现在业务层,将这种方式称为业务装配

好处:将多表联合查询转成了单表查询,便于书写SQL语句。

缺陷:①提升了业务层的代码量。②对数据库的IO操作非常频繁,SQL语句被执行了N+1次。

(二)ResultMap嵌套查询(N+1方式)

问题:在学习了业务装配方式后,其实就是将多表联合查询拆分单表查询,然后在业务层将数据根据表关系进行填充装配。那么,能不能把在业务层装配的动作发生在数据库呢?这样简化了业务层的代码压力。

解决:使用ResultMap N+1方式

概念:因为数据库层的代码是基于SqlSession对象动态生成的,所以需要我们手动声明业务装配的注入规则。使用resultMap标签声明规则,把此种方式称为ResultMap N+1方式。



    
    
    
    

(三)ResultMap嵌套结果


    
    
    
    
    
        
        
    

七、Mybatis的注解

概念:所谓注解其实就是在代码中使用特殊的配置方式来替换我们在XML文件中的配置信息

好处:①简单好用②保护原始配置文件③提升开发效率

缺点:配置信息和代码的耦合性变高了。注解配置的信息一旦复杂,阅读性降低。

使用时机:建议单表的增删改查,而且不经常迭代更新的代码,可以考虑使用注解,注解和配置文件的区别。注解是用来替换XML的配置信息,同一个配置信息要么使用注解,要么使用XML配置。在同一个项目中注解和XML配置是可以同时存在的。

public interface FlowerMapper{
    //查询注解
    @Select("select * from flower")
    List getFlowerInfo();
    @Select("select * from flower where id=#{0} and name=#{1}")
    Flower getFlowerById(int id,String name);   
    //增加注解
    @Insert("insert into flower values (default,#{0},#{1},#{2})")
    int insFlower(String name,double price,String production)
    //修改注解
    @Update("update flower set name=#{1} where id=#{0}")
    int upFlower(int id,String name);
    //删除注解
    @Delete("delete from flower where id=#{0}")
    int delFlower(int id);
}

八、Mybatis的缓存

缓存的概念:所谓缓存其实就是将经常被操作的数据临时存储到当前应用程序的内存空间提升数据的读取效率。数据是临时数据,应用程序关闭,则数据丢失,除非在应用程序关闭的时候进行数据的持久化存储

缓存文件:存储缓存数据的持久化文件,删除不会影响程序的正常使用,数据存储的位置:电脑内存

特点:缓存的数据不会永久保存,除非数据持久化缓存的空间是有上限的达到上限后,新的数据会覆盖原有数据。缓存可以提升数据的读取效率,降低IO操作的次数。

缓存由接口Cache定义,整个体系采用装饰器设计模式,数据存储和缓存的基本功能由PerpetualCache永久缓存(永久缓存:缓存能随意的写入硬盘,允许昂贵的创建数据来保持缓存,甚至能让应用重启)实现。

(一)一级缓存

一级缓存是基于PerpetualCache的HashMap本地缓存,其存储作用域为SqlSession,当SqlSession flush或close之后,该SqlSession中所有Cache就将清空。同一个SqlSession多次调用同一个Mapper和同一个方法的同一个参数,只会进行一次数据库查询,然后把数据缓存到缓存中,以后直接先从缓存中取出数据,不会直接去查询数据库,提升了数据的读取效率因为不同的SqlSession都是相互隔离的,所以如果使用的是不同的SqlSession对象操作相同的Mapper、参数和方法,他还是会再次发送到SQL到数据库去执行,返回结果。

存储作用域为SqlSession的原因:保存在执行器中,而执行器又在SqlSession中,所以一级缓存的生命周期与Sqlses-sion是相同的

public class TestMybatis{

    public static void main(String[] args) throws IOException{
        //获取SqlSession对象
        InputStream is = Resources.getResourceAsStream("mybatis.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession ss = factory.openSession();
        SqlSession ss2 = factory.openSession();

        //获取Mapper接口对象
        FlowerMapper fm = ss.getMapper(FlowerMapper.class);
        FlowerMapper fm2 = ss2.getMapper(FlowerMapper.class);

        //一级缓存:SqlSession对象
        //第一个SqlSession对象
        List lf1 = fm.selF();
        List lf2 = fm.selF();
        ss.close();
        //第二个SqlSession对象
        List lf3 = fm2.selF();
        List lf4 = fm2.selF();
        ss2.close();
    }
}

问题:不同的请求,服务器都会创建一个线程进行处理,每个线程都会创建一个SqlSession对象,如果请求的是相同的数据,则该数据会被缓存多次,造成空间浪费。

解决:开启二级factory缓存。

(二)二级缓存

二级缓默认也是采用PerpetualCache的HashMap缓存,不同在于其存储作用域为mapper,并且可自定义存储源,如Ech-ache。每个Mapper享有同一个二级缓存域。二级缓存以namespace名称空间为其唯一标示,被保存在configuration核心配置对象中,因此二级缓存的生命周期与SqlSessionFactory是相同的。在创建每个MapperedStatement对象时,都会根据其所属的namespace名称空间,给其分配Cache缓存对象。

public class Configuration{
    //(StricMap是Mybatis底层创建Map的一个类,封装了Map的创建)
    protected final Map x = new StricMap("Caches collection");
}

二级缓存是需要配置来开启的,在Mybatis.xml配置文件中加上以下代码:

然后在Mapper映射文件中添加一行

redOnly:只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势可读写的缓存会返回缓存对象的副本(通过序列化),这会慢一些,但是安全,因此默认是false。这里的缓存对象指的是:MapperStatement对象。

flushInterval(刷新间隔)可以被设置为任意的正整数,而且他们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目,默认值是1024。readOnly(只读)属性可以被设置为true或false。

如果已经开启二级缓存的mapper里面的某个查询不需要缓存,可以使用useCache=“false”禁用二级缓存。