Mybatis学习

目录

一、初识Mybatis

(一)Mybatis的原理

(二)Mybatis的配置

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

(一)无参数的

(二)有参数的

(三)SqlSession总结

1.单参数

2.多参数

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

四、MybatisSql语句的动态拼接

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

(一)Automapping自动注入

(二)自定义注入

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

(一)业务装配方式的联合查询

(二)ResultMap N+1方式联合查询

(三)ResultMap使用association标签的多表联合查询

七、Mybatis的注解学习

八、Mybatis的缓存

(一)一级缓存

(二)二级缓存

(三)刷新缓存

九、责任链和基于ThreadLocal的封装

(一)责任链

(二)ThreadLocal

(三)基于ThreadLocal的封装


一、初识Mybatis

(一)Mybatis的原理

在MyBatis运行开始时需要先通过Resources加载全局配置文件,然后需要实例化SqlSessionFactoryBuilder构建器帮助SqlSessionFactory接口实现类DefaultSqlSessionFactory,在实例化DefaultSqlSessionFactory之前需要先创建XmlConfigBuilder解析全局配置文件流,并把解析结果存放在Configuration中,之后把Configuratin传递给DefaultSqlSessionFactory到此SqlSessionFactory工厂创建成功。由SqlSessionFactory工厂创建SqlSession,每次创建SqlSession时,都需要由TransactionFactory创建Transaction对象,同时还需要创建SqlSession的执行器Excutor最后实例化DefaultSqlSession,传递给SqlSession接口,根据项目需求使用SqlSession接口中API完成具体的事务操作。如果事务执行失败,需要进行rollback回滚事务,如果事务执行成功提交给数据库,关闭SqlSession。

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

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

    /**
     *    作用:生产SqlSession对象,用来完成对数据库的操作
     *    内部操作:
     *         Environment:存储了数据库环境信息
     *         TransactionFactory:用来生产事务管理对象
     *         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对象的单表无参数增删改查 -->
        <!-- 查询 -->
        <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对象的单表有参数增删改查 -->
    <!-- 一个参数 -->
    <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.单参数

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

(2)在SQL语句中使用#{0}进行占位(基本类型的数据都可以使用角标来占位)

(3)使用SqlSession的方法完成查询,第一个参数为要执行的Sql语句的全限定路径第二个参数为接收的实参

2.多参数

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

(2)在Sql语句中使用#{属性名|健名}进行参数占位

(3)因为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>
    <!-- 单参数 -->
    <!-- 基本类型 -->
    <select id="selById" resultType="flower">
        select * from flower where id=#{0}
    </select>
    <!-- 对象类型 -->
    <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>
    <!-- 基本类型和对象混用 -->
    <select id="selByIdName3" resultType="flower">
        select * from flower where id=#{param1} and name=#{param2.name}
    </select>
    <!-- 注解方式 -->
    <select>
        select * from flower where id=#{id} and name=#{flower.name}
    </select>
</mapper>

(2)在java中的应用

public interface FlowerMapper{
    //无参数
    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标签。

<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中的多分支语句。此结构最多只能成立一个条件,只要一个成立,其他的就不会再判断执行了

<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给原有内容添加指定的前缀,prefixOverrides去除原有内容的指定前缀,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>标签:降低不同的select字句中的字段名的冗余,使用sql标签声明select子句的公共查询字段,然后使用include标签在select子句中引入公共字段声明。

<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>

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

(一)业务装配方式的联合查询

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

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

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

(二)ResultMap N+1方式联合查询

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

解决:使用ResultMap N+1方式

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

//代码走丢了

(三)ResultMap使用association标签的多表联合查询

<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操作的次数。

(一)一级缓存

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

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(Namespace),并且可自定义存储源,如Echache。即:二级缓存是mapper级别的缓存,它的实现机制跟一级缓存差不多,二级缓存是跨Session的每个Mapper享有同一个二级缓存域

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

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

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

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

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

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

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

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

(三)刷新缓存

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

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

九、责任链和基于ThreadLocal的封装

(一)责任链

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

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

(二)ThreadLocal

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

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

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

关于hashMap为什么线程不安全可以参考:http://www.cnblogs.com/shamo89/p/6684960.html

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){
            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 {}

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章