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

}

 

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