Java數(shù)據(jù)持久化之mybatis(12)

mybatis 動(dòng)態(tài)SQL以及和spring的集成

3.6 動(dòng)態(tài)SQL

有時(shí)候浑玛,靜態(tài)的 SQL 語(yǔ)句并不能滿足應(yīng)用程序的需求焙压。我們可以根據(jù)一些條件谆趾,來(lái)動(dòng)態(tài)地構(gòu)建 SQL 語(yǔ)句于购。

例如巩梢,在 Web 應(yīng)用程序中创泄,有可能有一些搜索界面,需要輸入一個(gè)或多個(gè)選項(xiàng)括蝠,然后根據(jù)這些已選擇的條件去執(zhí)行檢 索操作鞠抑。在實(shí)現(xiàn)這種類型的搜索功能,我們可能需要根據(jù)這些條件 來(lái)構(gòu)建動(dòng)態(tài)的 SQL 語(yǔ)句忌警。如果用戶提供了任何輸入條 件搁拙,我們需要將那個(gè)條件 添加到SQL語(yǔ)句的WHERE子句中。

MyBatis 通過(guò)使用if,choose,where,foreach,trim元素提供了對(duì)構(gòu)造動(dòng)態(tài) SQL 語(yǔ)句的高級(jí)別支持慨蓝。

3.6.1 if條件

if元素被用來(lái)有條件地嵌入 SQL 片段感混,如果測(cè)試條件被賦值為 true,則相應(yīng)地 SQL 片段將會(huì)被添加到 SQL 語(yǔ) 句中礼烈。

假定我們有一個(gè)課程搜索界面,設(shè)置了 講師(Tutor)下拉列表框婆跑,課程名稱(CourseName)文本輸入框此熬,開(kāi)始 時(shí)間(StartDate)輸入框,結(jié)束時(shí)間(EndDate)輸入框滑进,作為搜索條件犀忱。假定課講師下拉列表是必須選的,其他的 都是可選的扶关。

當(dāng)用戶點(diǎn)擊 搜索 按鈕時(shí)阴汇,我們需要顯示符合以下條件的成列表:

  • 特定講師的課程
  • 課程名 包含輸入的課程名稱關(guān)鍵字的課程;如果課程名稱輸入為空,則取所有課程
  • 在開(kāi)始時(shí)間和結(jié)束時(shí)間段內(nèi)的課程

我們可以對(duì)應(yīng)的映射語(yǔ)句节槐,如下所示:

    <resultMap type="Course" id="CourseResult">
        <id property="courseId" column="course_id" />
        <result property="name" column="name" />
        <result property="description" column="" />
        <result property="startDate" column="start_date" />
        <result property="endDate" column="end_date" />
    </resultMap>
    
    <select id="searchCourses" parameterType="hashMap" resultMap="CourseResult">
        select * from courses
            where tutor_id=#{tutorId} 
        <if test="courseName!=null">
            and name like #{courseName}
        </if>
        <if test="startDate!=null">
            and start_date <![CDATA[>=]]> #{startDate}
        </if>
        <if test="endDate!=null">
            and end_date <![CDATA[<=]]> endDate
        </if>
        
        
    </select>
public interface CourseMapper
{
    List<Course> searchCourses(Map<String, Object> map);
}
public void searchCourses()
{
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("tutorId", 1);
    map.put("courseName", "%java%");
    map.put("startDate", new Date());
    CourseMapper mapper = sqlSession.getMapper(CourseMapper.class);
    List<Course> courses = mapper.searchCourses(map);
    for (Course course : courses)
    {
        System.out.println(course);
    }
}

此處將生成查詢語(yǔ)句 SELECT * FROM COURSES WHERE TUTOR_ID= ? AND NAME like ? AND START_DATE >= ?搀庶。 準(zhǔn)備根據(jù)給定條件的動(dòng)態(tài) SQL 查詢將會(huì)派上用場(chǎng)。

 MyBatis 是使用 ONGL(Object Graph Navigation Language)表達(dá)式來(lái)構(gòu)建動(dòng)態(tài) SQL 語(yǔ)句

3.6.2 choose,when 和 otherwise 條件

有時(shí)候铜异,查詢功能是以查詢類別為基礎(chǔ)的哥倔。首先,用戶需要選擇是否希望通過(guò)選擇 講師揍庄,課程名稱咆蒿,開(kāi)始時(shí)間, 或結(jié)束時(shí)間作為查詢條件類別來(lái)進(jìn)行查詢,然后根據(jù)選擇的查詢類別沃测,輸入相應(yīng)的參數(shù)蒂破。在這樣的情景中,我們需要 只使用其中一種查詢類別寞蚌。

MyBatis 提供了choose元素支持此類型的 SQL 預(yù)處理.

現(xiàn)在讓我們書寫一個(gè)適用此情景的 SQL 映射語(yǔ)句田巴。如果沒(méi)有選擇查詢類別挟秤,則查詢開(kāi)始時(shí)間在今天之后的課程

<select id="searchCourses2" parameterType="hashMap" resultMap="CourseResult">
        select * from courses
            <choose>
                <when test="searchBy=='Tutor'">
                    where tutor_id=#{tutorId}
                </when>
                <when test="searchBy=='CourseName'">
                    where name like #{courseName}
                </when>
                <otherwise>
                    where start_date <![CDATA[>=]]> #{startDate}
                </otherwise>
            </choose>
        
        
    </select>

MyBatis 計(jì)算choose測(cè)試條件的值,且使用第一個(gè)值為 TRUE 的子句艘刚。如果沒(méi)有條件為 true管宵,則使用<otherwise> 內(nèi)的子句攀甚。

3.6.2 where條件

有時(shí)候秋度,所有的查詢條件(criteria)應(yīng)該是可選的。在需要使用至少一種查詢條件的情況下埠居,我們應(yīng)該使用 WHERE 子句事期。并且, 如果有多個(gè)條件绎橘,我們需要在條件中添加AND或OR唠倦。MyBatis提供了where元素支持這種類型的動(dòng) 態(tài) SQL 語(yǔ)句牵敷。
在我們查詢課程界面,我們假設(shè)所有的查詢條件是可選的靶瘸。進(jìn)而,當(dāng)需要提供一個(gè)或多個(gè)查詢條件時(shí)屋剑,應(yīng)該改使用 WHERE 子句诗眨。

<select id="searchCourses3" parameterType="hashMap" resultMap="CourseResult">
        select * from courses
        <where>
            <if test="tutorId!=null">
                tutor_id=#{tutorId}
            </if>
            <if test="courseName!=null">
                and name like #{courseName}
            </if>
            <if test="startDate!=null">
                and start_date <![CDATA[>=]]> #{startDate}
            </if>
            <if test="endDate!=null">
                and end_date <![CDATA[<=]]> #{endDate}
            </if>
            
        </where>
    </select>

where元素只有在其內(nèi)部標(biāo)簽有返回內(nèi)容時(shí)才會(huì)在動(dòng)態(tài)語(yǔ)句上插入 WHERE 條件語(yǔ)句匠楚。并且,如果 WHERE 子句以 AND 或者 OR 打頭峡懈,則打頭的 AND 或 OR 將會(huì)被移除与斤。
如果 tutor_id 參數(shù)值為 null撩穿,并且 courseName 參數(shù)值不為 null,則where標(biāo)簽會(huì)將 AND name like #{courseName} 中的 AND 移除掉雾狈,生成的 SQL WHERE 子句為:where name like #{courseName}抵皱。

3.6.4 trim條件

trim元素和where元素類似叨叙,但是<trim>提供了在添加前綴/后綴 或者 移除前綴/后綴方面提供更大的靈活 性堪澎。

   <select id="searchCourses" parameterType="hashMap" resultMap="CourseResult">
        select * from courses
        <trim prefix="where" prefixOverrides="AND | OR">
            <if test="tutorId!=null">
                tutor_id=#{tutorId}
            </if>
            <if test="courseName!=null">
                and name like #{courseName}
            </if>
            
        </trim>
    </select>

這里如果任意一個(gè)if條件為 true,trim元素會(huì)插入 WHERE,并且移除緊跟 WHERE 后面的 AND 或 OR

3.6.5 foreach 循環(huán)

另外一個(gè)強(qiáng)大的動(dòng)態(tài) SQL 語(yǔ)句構(gòu)造標(biāo)簽即是foreach钮呀。它可以迭代遍歷一個(gè)數(shù)組或者列表爽醋,構(gòu)造 AND/OR 條件或 一個(gè) IN 子句便脊。
假設(shè)我們想找到 tutor_id 為 1,3遂赠,6 的講師所教授的課程,我們可以傳遞一個(gè) tutor_id 組成的列表給映射語(yǔ) 句筷弦,然后通過(guò)<foreach>遍歷此列表構(gòu)造動(dòng)態(tài) SQL抑诸。

   <select id="searchCoursesByTutors1" parameterType="map" resultMap="CourseResult">
        select * from courses
            <if test="tutorIds!=null">
                <where>
                    <foreach collection="tutorIds" item="tutorId">
                        or tutor_id=#{tutorId}
                    </foreach>
                </where>
            </if>
    </select>
 public void searchCoursesByTutors() {
        Map<String, Object> map = new HashMap<>();
        List<Integer> tutorIds = new ArrayList<>();
        tutorIds.add(1);
        tutorIds.add(2);
        tutorIds.add(6);
        map.put("tutorIds", tutorIds);
        
        TutorService tutorService = new TutorService();
        List<Course> courses = tutorService.searchCoursesByTutors(map);
        System.out.println("++++");
        for (Course course : courses) {
            System.out.println("-----");
            System.out.println(course);
        }
    }

看一下怎樣使用<foreach>生成 IN 子句:

<select id="searchCoursesByTutors" parameterType="map" resultMap="CourseResult">
        select * from courses
            <if test="tutorIds!=null">
                <where>
                    tutor_id in
                        <foreach collection="tutorIds" item="tutorId" open="(" separator="," close=")">
                            #{tutorId}
                        </foreach>
                    
                </where>
            </if>
    </select>

3.6.6 set 條件:
set元素和where元素類似,如果其內(nèi)部條件判斷有任何內(nèi)容返回時(shí)奸绷,他會(huì)插入 SET SQL 片段异希。

   <update id="updateTutor" parameterType="Tutor">
        update tutors
        <set>
            <if test="name!=null">name=#{name},</if>
            <if test="email!=null">email=#{email},</if>
        </set>
        where tutor_id=#{tutorId}
    </update>

這里称簿,如果if條件返回了任何文本內(nèi)容,set將會(huì)插入set關(guān)鍵字和其文本內(nèi)容父虑,并且會(huì)剔除將末尾的 “士嚎,”悔叽。 在上述的例子中娇澎,如果 email!=null,set將會(huì)讓會(huì)移除email=#{email}后的逗號(hào)“,”,生成 set email=#{email} 括细。

3.7 mybatis 其它功能

除了簡(jiǎn)化數(shù)據(jù)庫(kù)編程外奋单,MyBatis 還提供了各種功能猫十,這些對(duì)實(shí)現(xiàn)一些常用任務(wù)非常有用呆盖,比如按頁(yè)加載表數(shù)據(jù)絮短,存取 CLOB/BLOB 類型的數(shù)據(jù)丁频,處理枚舉類型值邑贴,等等拢驾。讓我們來(lái)看看其中一些特性吧。

3.7.1 處理枚舉類型

MyBatis支持開(kāi)箱方式持久化enum 類型屬性咖为。假設(shè)STUDENTS表中有一列g(shù)ender(性別)類型為varchar躁染,存 儲(chǔ)”MALE”或者“FEMALE”兩種值架忌。并且叹放,Student 對(duì)象有一個(gè) enum 類型的 gender 屬性,如下所示:

public enum Gender
{
    FEMALE,
    MALE 
}

默認(rèn)情況下埋嵌,MyBatis 使用 EnumTypeHandler 來(lái)處理 enum 類型的 Java 屬性莉恼,并且將其存儲(chǔ)為 enum 值的名稱速那。你 不需要為此做任何額外的配置端仰。你可以可以向使用基本數(shù)據(jù)類型屬性一樣使用 enum 類型屬性田藐,代碼如下:

public class Student
{
    private Integer id;
    private String name;
    private String email;
    private PhoneNumber phone;
    private Address address;
    private Gender gender;
    //setters and getters
}

<insert id="insertStudent" parameterType="Student"
useGeneratedKeys="true" keyProperty="id">
    insert into students(name,email,addr_id, phone,gender)
    values(#{name},#{email},#{address.addrId},#{phone},#{gender})
</insert>

當(dāng)你執(zhí)行 insertStudent 語(yǔ)句的時(shí)候,MyBatis 會(huì)取 Gender 枚舉(FEMALE/MALE)的名稱踊餐,然后將其存儲(chǔ)到 GENDER 列中吝岭。
如果你希望存儲(chǔ)原 enum 的順序位置吧寺,而不是 enum 名稚机,你需要明確地配置它。
如果你想存儲(chǔ) FEMALE 為 0失乾,MALE 為 1 到 gender 列中碱茁,你需要在 mybatis-config.xml 文件中配置 EnumOrdinalTypeHandler:

<typeHandler
handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
javaType="com.mrq.domain.Gender"/>

 使用順序位置為值存儲(chǔ)到數(shù)據(jù)庫(kù)時(shí)要當(dāng)心蕾额。順序值是根據(jù) enum 中的聲明順序賦值 的诅蝶。如果你改變了 Gender enum 的聲明順序,則數(shù)據(jù)庫(kù)存儲(chǔ)的數(shù)據(jù)和此順序值就 不匹配了语盈。

3.7.2 傳入多個(gè)輸入?yún)?shù)

MyBatis 中的映射語(yǔ)句有一個(gè) parameterType 屬性來(lái)制定輸入?yún)?shù)的類型刀荒。如果我們想給映射語(yǔ)句傳入多個(gè)參數(shù)的 話缠借,我們可以將所有的輸入?yún)?shù)放到 HashMap 中宜猜,將 HashMap 傳遞給映射語(yǔ)句姨拥。

MyBatis 還提供了另外一種傳遞多個(gè)輸入?yún)?shù)給映射語(yǔ)句的方法渠鸽。假設(shè)我們想通過(guò)給定的name和email信息查找 學(xué)生信息徽缚,定義查詢接口如下:

Public interface StudentMapper
{
    List<Student> findAllStudentsByNameEmail(String name, String email);
}

MyBatis 支持 將多個(gè)輸入?yún)?shù)傳遞給映射語(yǔ)句凿试,并以#{param}的語(yǔ)法形式引用它們:

<select id="findAllStudentsByNameEmail" resultMap="StudentResult">
    select stud_id, name,email, phone from Students
        where name=#{param1} and email=#{param2}
</select>

這里#{param1}引用第一個(gè)參數(shù) name屠阻,而#{param2}引用了第二個(gè)參數(shù) email红省。

StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.
class);
studentMapper.findAllStudentsByNameEmail(name, email);

3.7.2 多行結(jié)果集映射成 Map

如果你有一個(gè)映射語(yǔ)句返回多行記錄国觉,并且你想以 HashMap 的形式存儲(chǔ)記錄的值麻诀,使用記錄列名作為 key 值蝇闭,而記 錄對(duì)應(yīng)值或?yàn)?value 值。我們可以使用 sqlSession.selectMap(),如下所示:

<select id=" findAllStudents" resultMap="StudentResult">
    select * from Students
</select>
Map<Integer, Student> studentMap =
sqlSession.selectMap("com.mrq.mappers.StudentMapper.findAllStudents", "studId");

這里 studentMap 將會(huì)將 studId 作為 key 值礼仗,而 Student 對(duì)象作為 value 值元践。

3.7.4 使用 RowBounds 對(duì)結(jié)果集進(jìn)行分頁(yè)

有時(shí)候童谒,我們會(huì)需要跟海量的數(shù)據(jù)打交道饥伊,比如一個(gè)有數(shù)百萬(wàn)條數(shù)據(jù)級(jí)別的表琅豆。由于計(jì)算機(jī)內(nèi)存的現(xiàn)實(shí)我們不可能一 次性加載這么多數(shù)據(jù),我們可以獲取到數(shù)據(jù)的一部分粒氧。特別是在 Web 應(yīng)用程序中外盯,分頁(yè)機(jī)制被用來(lái)以一頁(yè)一頁(yè)的形式展 示海量的數(shù)據(jù)翼雀。

MyBatis 可以使用 RowBounds 逐頁(yè)加載表數(shù)據(jù)狼渊。RowBounds 對(duì)象可以使用 offset 和 limit 參數(shù)來(lái)構(gòu)建狈邑。參數(shù) offset 表示開(kāi)始位置,而 limit 表示要取的記錄的數(shù)目糕伐。
假設(shè)如果你想每頁(yè)加載并顯示 25 條學(xué)生的記錄良瞧,你可以使用如下的代碼:

<select id="findAllStudents" resultMap="StudentResult">
    select * from Students
</select>

然后训唱,你可以加載如下加載第一頁(yè)數(shù)據(jù)(前 25 條):

int offset =0 , limit =25;
RowBounds rowBounds = new RowBounds(offset, limit);
List<Student> = studentMapper.getStudents(rowBounds);
若要展示第二頁(yè)况增,使用 offset=25,limit=25;第三頁(yè)澳骤,則為 offset=50,limit=25誊锭。

3.7.5 使用 ResultSetHandler 自定義結(jié)果集 ResultSet 處理

MyBatis 在將查詢結(jié)果集映射到 JavaBean 方面提供了很大的選擇性丧靡。但是温治,有時(shí)候我們會(huì)遇到由于特定的目的戒悠,需 要我們自己處理 SQL 查詢結(jié)果的情況绸狐。MyBatis 提供了 ResultHandler 插件形式允許我們以任何自己喜歡的方式處理 結(jié)果集 ResultSet。
假設(shè)我們想從學(xué)生的 stud_id 被用作 key突琳,而 name 被用作 value 的 HashMap 中獲取到 student 信息拆融。

對(duì)于 sqlSession.select()方法镜豹,我們可以傳遞給它一個(gè) ResultHandler 的實(shí)現(xiàn),它會(huì)被調(diào)用來(lái)處理 ResultSet 的每一條記錄泰讽。

看一下怎么使用 ResultHandler 來(lái)處理結(jié)果集 ResultSet菇绵,并返回自定義化的結(jié)果咬最。

public Map<Integer, String> getTutorIdNameMap() {
        final Map<Integer, String> map = new HashMap<>();
        SqlSession sqlSession = MyBatisSqlSessionFactory.openSession();
        try {
            sqlSession.select("com.mrq.mappers.TutorMapper.findAllTutors", 
                    new ResultHandler<Tutor>() {

                        @Override
                        public void handleResult(ResultContext<? extends Tutor> context) {
                            // TODO Auto-generated method stub
                            Tutor tutor = context.getResultObject();
                            map.put(tutor.getTutorId(),tutor.getName());
                        }
                    });
        } finally {
            //sqlSession.close();
            MyBatisSqlSessionFactory.closeSqlSession();
        }
        return map;
    }
    

在上述的代碼中永乌,我們提供了匿名內(nèi)部 ResultHandler 實(shí)現(xiàn)類具伍,在 handleResult()方法中人芽,我們使用 context.getResultObject()獲取當(dāng)前的 result 對(duì)象萤厅,即 Tutor對(duì)象,因?yàn)槲覀兌x了 findAllTutors 映射語(yǔ) 句的 resultMap=”tutorResult“楼誓。對(duì)查詢返回的每一行都會(huì)調(diào)用 handleResult()方法疟羹,并且我們從 findAllTutors 對(duì)象 中取出 tutorId 和 name榄融,將其放到 map 中。

3.7.6 緩存

將從數(shù)據(jù)庫(kù)中加載的數(shù)據(jù)緩存到內(nèi)存中黄刚,是很多應(yīng)用程序?yàn)榱颂岣咝阅芏扇〉囊回炞龇āyBatis 對(duì)通過(guò)映射的 SELECT 語(yǔ)句加載的查詢結(jié)果提供了內(nèi)建的緩存支持畏邢。默認(rèn)情況下检吆,啟用一級(jí)緩存;即蹭沛,如果你使用同一個(gè) SqlSession 接口對(duì)象調(diào)用了相同的 SELECT 語(yǔ)句摊灭,則直接會(huì)從緩存中返回結(jié)果帚呼,而不是再查詢一次數(shù)據(jù)庫(kù)。

我們可以在 SQL 映射器 XML 配置文件中使用cache 元素添加全局二級(jí)緩存眷蜈。 當(dāng)你加入了cache 元素酌儒,將會(huì)出現(xiàn)以下情況:

  • 所有的在映射語(yǔ)句文件定義的select語(yǔ)句的查詢結(jié)果都會(huì)被緩存
  • 所有的在映射語(yǔ)句文件定義的insert,update 和delete語(yǔ)句將會(huì)刷新緩存
  • 緩存根據(jù)最近最少被使用(Least Recently Used忌怎,LRU)算法管理
  • 緩存不會(huì)被任何形式的基于時(shí)間表的刷新(沒(méi)有刷新時(shí)間間隔)柔袁,即不支持定時(shí)刷新機(jī)制
  • 緩存將存儲(chǔ)1024個(gè) 查詢方法返回的列表或者對(duì)象的引用
  • 緩存會(huì)被當(dāng)作一個(gè)讀/寫緩存捶索。這是指檢索出的對(duì)象不會(huì)被共享,并且可以被調(diào)用者安全地修改酝润,不會(huì)其他潛 在的調(diào)用者或者線程的潛在修改干擾璃弄。(即夏块,緩存是線程安全的)

可以通過(guò)復(fù)寫默認(rèn)屬性來(lái)自定義緩存的行為脐供,如下所示:

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

以下是對(duì)上述屬性的描述:

  • eviction:此處定義緩存的移除機(jī)制政己。默認(rèn)值是 LRU,其可能的值有:LRU(least recently used,最近最少使用),FIFO(first in first out,先進(jìn)先出)卵牍,SOFT(soft reference,軟引用)糊昙,WEAK(weakreference,弱引用)溅蛉。
  • flushInterval:定義緩存刷新間隔他宛,以毫秒計(jì)厅各。默認(rèn)情況下不設(shè)置队塘。所以不使用刷新間隔憔古,緩存 cache 只有調(diào)用語(yǔ)句的時(shí)候刷新鸿市。
  • size:此表示緩存 cache 中能容納的最大元素?cái)?shù)即碗。默認(rèn)值是 1024剥懒,你可以設(shè)置成任意的正整數(shù)初橘。
  • readOnly:一個(gè)只讀的緩存 cache 會(huì)對(duì)所有的調(diào)用者返回被緩存對(duì)象的同一個(gè)實(shí)例(實(shí)際返回的是被返回對(duì)象的一份引用)保檐。一個(gè)讀/寫緩存 cache 將會(huì)返回被返回對(duì)象的一分拷貝(通過(guò)序列化)崔梗。默認(rèn)情況下設(shè)置為 false炒俱∪ㄎ颍可能的值有 false 和 true峦阁。

一個(gè)緩存的配置和緩存實(shí)例被綁定到映射器配置文件所在的名空間(namespace)上榔昔,所以在相同名空間內(nèi)的所有語(yǔ)
句被綁定到一個(gè) cache 中瘪菌。

認(rèn)的映射語(yǔ)句的 cache 配置如下:

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

可以為任意特定的映射語(yǔ)句復(fù)寫默認(rèn)的 cache 行為;例如师妙,對(duì)一個(gè) select 語(yǔ)句不使用緩存默穴,可以設(shè)置 useCache=“false”蓄诽。

除了內(nèi)建的緩存支持仑氛,MyBatis 也提供了與第三方緩存類庫(kù)如 Ehcache,OSCache自阱,Hazelcast 的集成支持米酬。你可 以在MyBatis官方網(wǎng)站https://code.google.com/p/mybatis/wiki/Caches 上找到關(guān)于繼承第三方緩存類庫(kù)的更 多信息赃额。

四. mybatis與spring集成

MyBatis-Spring 是 MyBatis 框架的子模塊跳芳,用來(lái)提供與當(dāng)前流行的依賴注入框架 Spring 的無(wú)縫集成飞盆。

Spring 框架是一個(gè)基于依賴注入(Dependency Injection)和面向切面編程(Aspect Oriented Programming,AOP)的 Java 框架吓歇,鼓勵(lì)使用基于 POJO 的編程模型城看。另外测柠,Spring 提供了聲明式和編程式的事務(wù)管理 能力,可以很大程度上簡(jiǎn)化應(yīng)用程序的數(shù)據(jù)訪問(wèn)層(data access layer)的實(shí)現(xiàn)谒主。在章節(jié)中,我們將看到在基于 Spring 的應(yīng)用程序中使用 MyBatis 并且使用 Spring 的基于注解的事務(wù)管理機(jī)制赃阀。

4.1 在spring應(yīng)用程序中配置mybatis

4.1.1 安裝

使用 Maven 構(gòu)建工具瘩将,你可以配置 MyBatis 的 spring 依賴如下:

pom.xml:

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>4.3.0.RELEASE</spring.version>
    </properties>
  
  <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.6.1</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.1</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>
        
        <!--mybatis-spring適配器 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</version>
        </dependency>
        <!--Spring框架核心庫(kù) -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- aspectJ AOP 織入器 -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.9</version>
        </dependency>
        <!--Spring java數(shù)據(jù)庫(kù)訪問(wèn)包,在本例中主要用于提供數(shù)據(jù)源 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
    </dependencies>

4.1.2 配置 MyBatis Beans

spring-mvc.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">
        
        <context:annotation-config></context:annotation-config>
        
        <context:property-placeholder location="classpath:application.properties" />
        <context:component-scan base-package="com.mrq"></context:component-scan>
       
       <!-- ========================================配置數(shù)據(jù)源========================================= -->
    <!--定義一個(gè)jdbc數(shù)據(jù)源凹耙,創(chuàng)建一個(gè)驅(qū)動(dòng)管理數(shù)據(jù)源的bean -->
    <bean id="dataSource"
        class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <property name="driverClass" value="${jdbc.driverClassName}" />
        <property name="jdbcUrl" value="${jdbc.url}" />
        <property name="user" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="acquireIncrement" value="5"></property>
        <property name="initialPoolSize" value="10"></property>
        <property name="minPoolSize" value="5"></property>
        <property name="maxPoolSize" value="20"></property>
    </bean>
  
    <!-- ========================================分隔線========================================= -->
    
    <!-- ========================================針對(duì)myBatis的配置項(xiàng)============================== -->
    <!-- 配置sqlSessionFactory -->
    <!--創(chuàng)建一個(gè)sql會(huì)話工廠bean,指定數(shù)據(jù)源 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 實(shí)例化sqlSessionFactory時(shí)需要使用上述配置好的數(shù)據(jù)源以及SQL映射文件 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 自動(dòng)掃描com.mrq.mappers/目錄下的所有SQL映射的xml文件, 省掉Configuration.xml里的手工配置
        value="classpath:com/mrq/mappers/*.xml"指的是classpath(類路徑)下com.mrq.mappers包中的所有xml文件
        TutorMapper.xml位于com.mrq.mappers包下肖抱,這樣TutorMapper.xml就可以被自動(dòng)掃描
         -->
        <property name="mapperLocations" value="classpath:com/mrq/mappers/*.xml" />
    </bean>
    <!-- 配置掃描器 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 掃描com.mrq.mappers這個(gè)包以及它的子包下的所有映射接口類 -->
        <property name="basePackage" value="com.mrq.mappers" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>
    
    <!-- ========================================分隔線========================================= -->
<!--5 聲明式事務(wù)管理 -->
    <!--定義事物管理器备典,由spring管理事務(wù) -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--支持注解驅(qū)動(dòng)的事務(wù)管理,指定事務(wù)管理器 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!--6 容器自動(dòng)掃描IOC組件  -->
    <context:component-scan base-package="com.mrq"></context:component-scan>
    
    <!--7 aspectj支持自動(dòng)代理實(shí)現(xiàn)AOP功能 -->
    <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
        
</beans>

使用上述的 bean 定義意述,Spring 會(huì)使用如下配置屬性創(chuàng)建一個(gè) SqlSessionFactory 對(duì)象:

  • dataSource:它引用了 dataSource bean
  • typeAliases:它指定了一系列的完全限定名的類名列表提佣,用逗號(hào)隔開(kāi)吮蛹,這些別名將通過(guò)默認(rèn)的別名規(guī)則創(chuàng)建(將首字母小寫的非無(wú)完全限定類名作為別名)。
  • typeAliasesPackage:它指定了一系列包名列表拌屏,用逗號(hào)隔開(kāi)潮针,包內(nèi)含有需要?jiǎng)?chuàng)建別名的JavaBeans。
  • typeHandlers:它指定了一系列的類型處理器類的完全限定名的類名列表倚喂,用逗號(hào)隔開(kāi)每篷。
  • typeHandlersPackage: 它指定了一系列包名列表,用逗號(hào)隔開(kāi)端圈,包內(nèi)含有需要被注冊(cè)的類型處理器類焦读。
  • mapperLocations:它指定了 SQL 映射器 Mapper XML 配置文件的位置
  • configLocation:它指定了 MyBatisSqlSessionFactory 配置文件所在的位置。
  • MapperScannerConfigurer 來(lái)掃描包 (package)中的映射器 Mapper 接口舱权,并自動(dòng)地注冊(cè)矗晃。
  • 使用 Spring 的基于注解的事務(wù)處理機(jī)制來(lái)避免書寫上述的每個(gè)方法中控制事務(wù)的冗余代碼。為了能使用 Spring 的事務(wù)管理功能宴倍,我們需要在 Spring 應(yīng)用上下文中配置 TransactionManager bean 實(shí)體對(duì) 象:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

在 Spring 中使用基于注解的事務(wù)管理特性张症,如下:

<tx:annotation-driven transaction-manager="transactionManager"/>

現(xiàn)在你可以在 Spring service bean 上使用@Transactional 注解,表示在此 service 中的每一個(gè)方法都應(yīng)該在 一個(gè)事務(wù)中運(yùn)行鸵贬。如果方法成功運(yùn)行完畢吠冤,Spring 會(huì)提交操作。如果有運(yùn)行期異常發(fā)生恭理,則會(huì)執(zhí)行回滾操作。另外郭变, Spring 會(huì)將 MyBatis 的異常轉(zhuǎn)換成合適的 DataAccessExceptions颜价,這樣會(huì)為特定錯(cuò)誤上提供額外的信息。

package com.mrq.service;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.mrq.dao.TutorDao;
import com.mrq.domain.Tutor;

@Service
public class TutorService {
    
    
    private TutorDao tutorDao;
    
    @Autowired
    public void setTutorDao(TutorDao tutorDao) {
        this.tutorDao = tutorDao;
    }
    
    public Tutor findTutorById(int tutorId) {
        return tutorDao.findTutorById(1);
    }
    
    @Transactional
    public int  insertTutor(Tutor tutor) {
        return tutorDao.insertTutor(tutor);
    }
}

寫一個(gè)獨(dú)立的測(cè)試客戶端來(lái)測(cè)試 TutorService诉濒,如下:

package com.mrq.main;

import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.mrq.domain.Tutor;
import com.mrq.service.TutorService;

public class TestTutorService {
    
    static TutorService tutorService; 
    @BeforeClass 
    public static void before() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring-mvc.xml");
        tutorService = (TutorService) ctx.getBean("tutorService");
    }
    
    @Test
    public void testFindTutor() {
        Tutor tutor = tutorService.findTutorById(1);
        System.out.println(tutor+"---====");
    }
    
    @Test
    public void insertTutor() {
        Tutor tutor = new Tutor();
        tutor.setEmail("Piter@163.com");
        tutor.setName("Piter");
        int count = tutorService.insertTutor(tutor);
        System.out.println("insert-> "+count);
    }

}

domain:

public class Tutor {
    private Integer tutorId;
    private String name;
    private String email;
    private String phone;
    
    //setter and getter
}

dao:

package com.mrq.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.mrq.domain.Tutor;
import com.mrq.mappers.TutorMapper;

@Repository
public class TutorDao {
    private TutorMapper tutorMapper;
    
    @Autowired
    public void setTutorMapper(TutorMapper tutorMapper) {
        this.tutorMapper = tutorMapper;
    }
    public Tutor findTutorById(int tutorId) {
        return tutorMapper.findTutorById(1);
    }
    
    public int  insertTutor(Tutor tutor) {
        return tutorMapper.insertTutor(tutor);
    }
}

service:

package com.mrq.service;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.mrq.dao.TutorDao;
import com.mrq.domain.Tutor;

@Service
public class TutorService {
    
    
    private TutorDao tutorDao;
    
    @Autowired
    public void setTutorDao(TutorDao tutorDao) {
        this.tutorDao = tutorDao;
    }
    
    public Tutor findTutorById(int tutorId) {
        return tutorDao.findTutorById(1);
    }
    
    @Transactional
    public int  insertTutor(Tutor tutor) {
        return tutorDao.insertTutor(tutor);
    }
}

mapper接口

package com.mrq.mappers;

import com.mrq.domain.Tutor;

public interface TutorMapper {
    Tutor findTutorById(int tutorId);
    int insertTutor(Tutor tutor);
}

映射文件TutorMapper.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.mrq.mappers.TutorMapper">

    <resultMap type="com.mrq.domain.Tutor" id="TutorResult">
        <id property="tutorId" column="tutor_id" />
        <result property="name" column="name" />
        <result property="email" column="email" />
        
    </resultMap>
    
    <select id="findTutorById" parameterType="int" resultMap="TutorResult">
        select T.tutor_id,T.name,email
            from tutors T where T.tutor_id=#{tutorId}
    </select>
    
    <insert id="insertTutor" parameterType="com.mrq.domain.Tutor">
        insert into tutors(name,email) values(#{name},#{email})
    </insert>
    
</mapper>

至此,一個(gè)簡(jiǎn)單的spring和mybatis集成的小demo完成了.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末周伦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子未荒,更是在濱河造成了極大的恐慌专挪,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件片排,死亡現(xiàn)場(chǎng)離奇詭異寨腔,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)率寡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門迫卢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人冶共,你說(shuō)我怎么就攤上這事乾蛤∶拷纾” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵家卖,是天一觀的道長(zhǎng)眨层。 經(jīng)常有香客問(wèn)我,道長(zhǎng)上荡,這世上最難降的妖魔是什么趴樱? 我笑而不...
    開(kāi)封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮榛臼,結(jié)果婚禮上伊佃,老公的妹妹穿的比我還像新娘。我一直安慰自己沛善,他們只是感情好航揉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著金刁,像睡著了一般帅涂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上尤蛮,一...
    開(kāi)封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天媳友,我揣著相機(jī)與錄音,去河邊找鬼产捞。 笑死醇锚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的坯临。 我是一名探鬼主播焊唬,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼看靠!你這毒婦竟也來(lái)了赶促?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤挟炬,失蹤者是張志新(化名)和其女友劉穎鸥滨,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體谤祖,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡婿滓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粥喜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片空幻。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖容客,靈堂內(nèi)的尸體忽然破棺而出秕铛,到底是詐尸還是另有隱情约郁,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布但两,位于F島的核電站鬓梅,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏谨湘。R本人自食惡果不足惜绽快,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望紧阔。 院中可真熱鬧坊罢,春花似錦、人聲如沸擅耽。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)乖仇。三九已至憾儒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間乃沙,已是汗流浹背起趾。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留警儒,地道東北人训裆。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蜀铲,于是被迫代替她去往敵國(guó)和親边琉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1. 簡(jiǎn)介 1.1 什么是 MyBatis 蝙茶? MyBatis 是支持定制化 SQL、存儲(chǔ)過(guò)程以及高級(jí)映射的優(yōu)秀的...
    笨鳥(niǎo)慢飛閱讀 5,523評(píng)論 0 4
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理诸老,服務(wù)發(fā)現(xiàn)隆夯,斷路器,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • 這部分主要是開(kāi)源Java EE框架方面的內(nèi)容别伏,包括Hibernate蹄衷、MyBatis、Spring厘肮、Spring ...
    雜貨鋪老板閱讀 1,385評(píng)論 0 2
  • Java數(shù)據(jù)持久化之mybatis 一. mybatis簡(jiǎn)介 1.1 原始的JDBC操作: Java 通過(guò) Jav...
    小Q逛逛閱讀 4,919評(píng)論 0 16
  • 內(nèi)容
    消釋的閱讀 126評(píng)論 0 0