Mybatis

本文深入解析MyBatis框架的原理与配置,涵盖单表与多表查询、动态SQL、缓存机制、注解使用及高级特性。通过具体案例,讲解如何运用MyBatis进行高效数据库操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、初识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>中的每一个标签都对应一个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的配置

<?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>
    <!-- 开启log4j 默认是开启的 -->
    <settings>
        <setting name="logImpl" value="LOG4J" />
    </stetings>
    <!-- 设置实体类别名 -->
    <typeAliases>
        <!-- 给这个包下的类统一设置别名 -->
        <package name="com.bjsxt.pojo">
        <!-- 给某一个类单独设置别名,一般用于不同包的相同类名 -->
        <typeAliase type="com.bjsxt.pojo.Flower" alias="f" />
    </typeAliases>
    <!-- 配置数据库环境 可以配置多个数据库环境 default表示要使用的数据库id -->
    <environments default="mysql">
        <!-- 设置具体的数据库环境 id唯一的标识一个数据库配置 -->
        <environment id="mysql">
            <!-- 设置事务管理 type:JDBC是原生的JDBC事务管理方式 MANAGED将事务管理交由容器管理,比如Spring -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 设置数据源 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.bjsxt.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
                <property name="username" value="root"/>
                <property name="password" value="bjsxt">
            </dataSource>
        </environment>
    </environments>
    <!-- 配置mapper扫描 会扫描com.bjsxt.mapper下面的所有mapper.xml文件 -->
    <mappers>
        <package name="com.bjsxt.mapper" />
    </mappers>
</configruation>

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

(一)无参数的

(1)mapper.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.bjsxt.mapper.FlowerMapper">
    <!-- 基于SqlSession对象的单表无参数增删改查 -->
        <!-- 
            查询 :
            resultType:配置查询结果的类型。可以写mybatis.xml中配置的别名,或者全限定路径名
                        用于mybatis底层,通过反射获得对象成员属性、方法以及表名、字段名 
        -->
        <select id="selAll" resultType="flower">
            select * from flower
        </select>
        <!-- 增加 -->
        <insert id="insF">
            insert into flower values(default,'随便花',199,'bjsxt')
        </insert>
        <!-- 修改 -->
        <update id="upF">
            update flower set name='随便花' where id = 6
        </update>
        <!-- 删除 -->
        <delete id="delF">
            dalete from flower where id = 6
        </delete>
</mapper>

(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<Flower> 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文件的配置

<?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.bjsxt.mapper.FlowerMapper">
    <!-- 
        基于SqlSession对象的单表有参数增删改查:   
            1.parameterType:
                参数类型(基本类型、Object、Map)
            2.SqlSession只能传一个参数:
                ①传基本类型,通过{0}、角标取值
                ②传对象,通过#{属性名}取值
                ③传map集合,通过#{健名}取值(将参数封装到map中传给sqlsession)
     -->
    <!-- 一个参数 -->
    <select id="selF" resultType="flower" parameterType="int">
        select * from flower where id=#{0}
    </select>
    <!-- 多个参数 -->
    <select id="selF2" resultType="flower" parameterType="flower">
        select * from flower where id=#{id} and name=#{name}
    </select> 
    <insert id="insF2" parameterType="map">
        insert into flower values(default,#{name},#{price},#{producation})
    </insert>
</mapper>

(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<String,Object> map = new HashMap<String,Object>();
        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文件的配置

<?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.bjsxt.mapper.FlowerMapper">
    <!-- 无参数 -->
    <select id="selF" resultType="flower">
        select * from flower
    </select>
    <!-- 单参数 -->
    <!-- 基本类型 #{0} 会进行占位 -->
    <select id="selById" resultType="flower">
        select * from flower where id=#{0}
    </select>
    <!-- 对象类型 ${id} 会进行赋值 -->
    <select id="selByIdName" resultType="flower">
        select * from flower where id='${id}' and name='${name}'
    </select>
    <!-- 多参数 -->
    <!-- 基本类型 -->
    <select id="selByIdName2" resultType="flower">
        select * from flower where id=#{0} and name=#{1}
    </select>
    <!-- 基本类型和对象混用 #{param1}为第一个参数 #{param2}第二个参数 -->
    <select id="selByIdName3" resultType="flower">
        select * from flower where id=#{param1} and name=#{param2.name}
    </select>
    <!-- 注解方式 #{id}注解别名 映射形参 -->
    <select>
        select * from flower where id=#{id} and name=#{flower.name}
    </select>
</mapper>

(2)在java中的应用

public interface FlowerMapper{
    //注意:形参名和xml中的键没有任何关系!只能用@Param()建立映射联系
    //无参数
    List<Flower> 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)<if>标签test中可以直接书写逻辑判断,但是连接符是and或者or,可以使用param1|键名|属性名|注解别名获取参数的值。

<select id="selByNamePro" resultType="flower">
    select * from flower where 1 = 1
    <if test="param1 != '' and param1 != null">
        and name=#{param1}
    </if>
    <if test="param2 != '' and param2 != null">
        and production=#{param2}
    </if>
</select>

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

<select id="selWhere" resultType="flower">
    select * from flower
    <where>
        <if test="param1 != '' and param1 != null">
            and name=#{param1}
        </if>
        <if test="param2 != '' and param2 != null">
            and production=#{param2}
        </if>
    </where>
</select>

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

<select id="selChoose" resultType="flower">
    select * from flower
    <where>
        <choose>
            <when test="param1 != '' and param1 != null">
                and name=#{param1}
            </when>
            <when test="param2 != '' and param2 != null">
                and production=#{param2}
            </when>
            <otherwise>
                and 1 = 1
            </otherwise>
        </choose>
    </where>
</select>

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

<update id="upF">
    update flower 
        <set> 
            id=#{param3},
            <if test="param1 != '' and param1 != null">
                name=#{param1},
            </if>
            <if test="param2 != '' and param2 != null">
                production=#{param2}
            </if>
        </set> 
    where id=#{param3}
</pudate>

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

<update id="upTrim">
    update flower
        <trim prefix="set" suffixOverrides=",">
            id=#{param3},
            <if test="param1 != '' and param1 != null">
                name=#{param1},
            </if>
            <if test="param2 != '' and param2 != null">
                production=#{param2},
            </if>
        </trim>
    where id=#{param3}
</update>

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

<select id="selByIdAll" resultType="flower">
    select * from flower where id in
    <foreach collection="array" item="item" open="(" close=")" separator=",">
        #{item}
    </foreach>
</select>

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

<select id="selBind" reaultType="flower">
    <bind name="pm" value="'%'+name+'%'" />
    select * from flower where name like #{pm}
</select>

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

<select id="selAll" resultType="flower">
    select id, name, <include refid="my"></include> from flower
</select>
<sql id="my">
    price, production
</sql>

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

(一)Automapping自动注入

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

(二)自定义注入

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

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

<!-- id标签:声明主键的注入规则 -->
<!-- result标签:声明普通字段的注入规则 -->
<!-- association标签:联合查询使用,声明普通对象的注入规则 -->
<!-- collection标签:声明集合对象的注入规则 用于一对多关系-->
<resultMap type="student" id="rm">
    <id property="sid" column="sid">
    <result property="sname" column="sname" />
    <result property="sage" column="sage" />
    <result property="tid" column="tid" />
    <association property="teadcher" javaType="teacher">
        <id property="tid" column="tid" />
        <result property="tname" column="tname" />
    </association>
</resultMap>
<select id="selStuTea" reaultMap="rm">
    select * from student s
    join teacher t on s.tid = t.tid
    <where>
        <if test="sanme != '' and sanme !== null">
            <bind name="sa" value="'%'+sanme+'%'" />
            and s.sname like #{sa}
        </if>
        <if test="tanme != '' and tanme != nu;;">
            <bind name="ta" value="'%'+tname+'%'">
            and t.tname like #{ta}
        </if>
    </where>
    limit #{pageStart},#{pageSize}
</select>

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

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

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

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

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

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

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

解决:使用ResultMap N+1方式

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


<resultMap type="student" id="rm">
    <id property="sid" column="sid">
    <result property="sname" column="sname" />
    <result property="sage" column="sage" />
    <association property="teacher" column="tid" select="com.xx.xx.selT2"></association>
</resultMap>
<select id="selStuTea" reaultMap="rm">
    select * from student s
    join teacher t on s.tid = t.tid
    <where>
        <if test="sanme != '' and sanme !== null">
            <bind name="sa" value="'%'+sanme+'%'" />
            and s.sname like #{sa}
        </if>
    </where>
    limit #{pageStart},#{pageSize}
</select>

(三)ResultMap嵌套结果

<resultMap type="student" id="rm">
    <id property="sid" column="sid">
    <result property="sname" column="sname" />
    <result property="sage" column="sage" />
    <result property="tid" column="tid" />
    <association property="teadcher" javaType="teacher">
        <id property="tid" column="tid" />
        <result property="tname" column="tname" />
    </association>
</resultMap>
<select id="selStuTea" reaultMap="rm">
    select * from student s
    join teacher t on s.tid = t.tid
    <where>
        <if test="sanme != '' and sanme !== null">
            <bind name="sa" value="'%'+sanme+'%'" />
            and s.sname like #{sa}
        </if>
    </where>
    limit #{pageStart},#{pageSize}
</select>

七、Mybatis的注解

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

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

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

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

public interface FlowerMapper{
    //查询注解
    @Select("select * from flower")
    List<Flower> 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<Flower> lf1 = fm.selF();
        List<Flower> lf2 = fm.selF();
        ss.close();
        //第二个SqlSession对象
        List<Flower> lf3 = fm2.selF();
        List<Flower> 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<String,Cache> x = new StricMap<Cache>("Caches collection");
}

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

<setting name="cacheEnabled" value="true" />

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

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true" />

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

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

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

<select id="selById" resultMap="mp" parameterType="java.lang.String" useCache="false">

(三)清空缓存

在mapper的同一个namespace中,如果有其他C/U/D操作后都需要执行刷新缓存操作避免脏读。无论是一级缓存还是二级缓存,C/U/D操作commit提交前会清空缓存区域使缓存失效。(commit会清空一级缓存二级缓存,flush会清空一二级缓存执行SQL语句,但是不会将当前事务加进事物管理中,)合理利用二级缓存可以提高系统性能,减少数据库压力。但是,如果使用不当可能会出现缓存一致性问题,对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度。

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

我们系统为了提高系统开发、性能,一般对项目进行分布式部署。缓存在各个服务单独存储,不方便开发,所以要使用分布式缓存对缓存数据进行集中管理,mybatis无法实现分布式缓存,所以需要用到redis(实现了Mybatis的自定义缓存)、EhCache、merncache等。

1.flush()对缓存的影响

flush做了两件事:①清空缓存②执行SQL。这样可以避免脏读。会清空一级缓存,不会影响二级缓存。但是这个时候的SQL语句,只是从缓存中放到了事务中,并没有提交到数据库。

3.close()对缓存的影响

close()会将其一级缓存放进二级缓存中,SqlSession关了,一级缓存就不存在了。执行close时,如果autoCommit为false,只会清空自身的缓存,为true,会清空二级缓存。

3.commit()对缓存的影响

commit()会将其一级缓存放进二级缓存中,并清空二级缓存。会清空二级缓存,清空一级缓存(这是执行增删改SQL的效果)。

九、基于ThreadLocal的封装

(一)ThreadLocal

在解决多线程安全问题的时候,为了让线程共享资源,必须小心的对共享资源进行同步,同步带来一定的效能延迟,而另一个方面,在处理同步的时候,又要注意对象的锁定与释放,避免产生死结,种种因素都使得多线程程序变得困难。尝试从另一个角度来思考多线程共享资源的问题既然共享资源这么困难,那么就干脆不要共享,为每个线程创造一个资源的副本。将每个线程存取数据的行为加以隔离,实现的方法就是给予每个线程一个特定空间来保管该线程所独享的资源。

ThreadLocal的功用非常简单,就是每一个使用该变量的线程都提供一个变量值得副本,使每一个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。

ThreadLocal的原理:在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本比如下面的示例实现:

public class ThreadLocal{
    //synchronizedMap这里用了装饰者模式,包装了hashmap,基类是不同步的,包装器是同步的
    private Map values = Collections.synchronizedMap(new HashMap());
    public Object get(){
        //currenThread()返回对当前正在执行的线程对象的引用
        Thread curThread = Thread.currentThread();
        Object o = values.get(curThread);
        //如果map集合中的值o为null 并且 map集合不包含curThread这个键 那么执行if里面的语句
        if(o == null && values.containsKey(curThread)){
            o = initialValue();
            values.put(curThread,o);
        }
        return o;
    }
    //将当前线程和线程安全的变量放进map集合
    public void set(Object newValue){
        values.put(Thread.currenThread(),newValue);
    }
    //初始化Map的值 也就是ThreadLocal想要实现线程安全的变量
    public Object initialValue(){
        return null;
    }
}

(三)基于ThreadLocal的封装

问题:目前我们使用Mybatis进行数据库操作,在业务层书写的代码,会造成,每个请求都会创建SqlSessionFactory对象,这样造成factory的缓存(二级缓存)起不了作用。

解决:①不同的线程需要使用同一个SqlSessionfactory对象,使factory线程共享。②不同的线程需要创建不同的SqlSession对象,使SqlSession非线程共享。③同一个线程内获取同一个SqlSession对象,使SqlSession对象线程内共享。(针对第三点:传参也可以保住获取的是同一个对象,但是麻烦,如果我们使用一个封装好的类,对外提供一个静态的方法来获取该对象,也可以保证对象的唯一性。在下面的封装中,使用的是单例模式

(1)MybatisUtil

public class MybatisUtil{

    //声明静态的factory属性
    private static SqlSessionFactory factory;
    //声明静态的ThreadLocal属性
    //static修饰的对象在内存中只会有一个拷贝,所以static修饰的
    private static ThreadLocal<SqlSession> ts = new ThreadLocal<SqlSession>();
    //声明静态代码块
    static{
        //获取流对象
        try{
            InpuStream is = Resources.getResourceAsStream("mybatis.xml");
            factory = new SqlSessionFactoryBuilder().build(is);
        }catch(IOException e){
            e.printStackTrace();
        }
    }
    //封装方法获取SqlSession对象
    public static SqlSession getSqlSession(){
        SqlSession ss = ts.get();
        if(ss == null){
           /**
            * openSession()通过传参设置autoCommit的值,模式为false,关闭自定提交。
            *     false:当前所有操作作为一个事务,当执行增删改时,发生异常时,
            *           会执行rollback(),之前所有对数据库的操作全部取消。
            *     true:自动事务,后面对数据库的操作发生异常,不会影响前面对
            *           数据库的操作。rollback只会回滚当前一个事务。
            */
            ss = fatory.openSession();
            ts.set(ss);
        }
        return ts.get();
    }
    //关闭SqlSession对象
    public static void closeSqlSession(){
        SqlSeesion ss = ts.get();
        if(ss != null){
            ss.close();
            ts.set(null);
        }
    }
}

(2)过滤器调用实现

@WebFilter("/*")
public class MyFilter implements Filter{
    @Override
    public void destroy() {}

    @Override
    public void doFilter(ServletRequest req,ServletResponse resp,FilterChain chain)
                    throws IOException,ServletException{
        //放行
        try{
            //设置编码格式
            chain.doFilter(req,resp);
            //提交
            MybatisUtil.getSqlSession().commit();
            //关闭SqlSession对象
            MybatisUtil.closeSqlSession();
        }catch(Exception e){
            MybatisUtil.getSqlSession().rollback();
            MybatisUtil.closeSqlSession();
        }
    }
    @Override
    public void init(FilterConfig arg0) throws ServletException {}

}

十、责任链设计模式

责任链概念:(23种设计模式中的一种,是行为型模式)一个功能的完整处理,需要多个对象之间的联动操作,而每个对象负责的处理内容不同,或者说,责任不同,并且形成了一个链条式的执行机制,我们把该链条称为责任链

基于MVC的Web开发的责任链的特点:一个请求,一个线程,一条责任链。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值