MyBatis3:SQL映射


前言

前面學習了config.xml,下面就要進入MyBatis的核心SQL映射了,第一篇文章的時候,student.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.xrq.StudentMapper">
    <select id="selectStudentById" parameterType="int" resultType="Student">
        <![CDATA[
            select * from student where studentId = #{id}
        ]]>
    </select>
</mapper>

基於這個xml,進行擴展和學習。

 

為什么要使用<![CDATA[ ... ]]>?

上面的配置文件中,大家一定注意到了一個細節,就是SQL語句用<![CDATA[ ... ]]>這對標簽包含起來了,那么為什么要這么做呢?不妨把上面內容稍微修改一下:

<?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.xrq.StudentMapper">
    <select id="selectStudentById" parameterType="int" resultType="Student">
        select * from student where studentId = #{id} or studentAge < 10 or studentAge > 20;
    </select>
</mapper>

當然這句SQL語句沒有任何含義,只是瞎寫的演示用而已,運行一下看一下結果:

Exception in thread "main" java.lang.ExceptionInInitializerError
    at com.xrq.test.MyBatisTest.main(MyBatisTest.java:9)
Caused by: org.apache.ibatis.exceptions.PersistenceException: 
### Error building SqlSession.
### The error may exist in student.xml
...

后面的異常信息就不列了。按理說很正常的一句SQL語句,怎么會報錯呢?仔細想來,錯誤的根本原因就是student.xml本身是一個xml文件,它並不是專門為MyBatis服務的,它首先具有xml文件的語法。因此,"< 10 or studentAge >"這段,會先被解析為xml的標簽,xml哪有這種形式的標簽的?所以當然報錯了。

所以,使用<![CDATA[ ... ]]>,它可以保證如論如何<![CDATA[ ... ]]>里面的內容都會被解析成SQL語句。因此,建議每一條SQL語句都使用<![CDATA[ ... ]]>包含起來,這也是一種規避錯誤的做法。

 

select

SQL映射中有幾個頂級元素,其中最常見的四個就是insert、delete、update、select,分別對應於增、刪、改、查,下面先對於select元素進行學習。

1、多條件查詢查一個結果

前面的select語句只有一個條件,下面看一下多條件查詢如何做,首先是student.xml:

<select id="selectStudentByIdAndName" parameterType="Student" resultType="Student">
    <![CDATA[
        select * from student where studentId = #{studentId} and studentName = #{studentName};
    ]]>
</select>

注意這里的parameter只能是一個實體類,然后參數要和實體類里面定義的一樣,比如studentId、studentName,MyBatis將會自動查找這些屬性,然后將它們的值傳遞到預處理語句的參數中去。

還有一個很重要的地方是,使用參數的時候使用了"#",另外還有一個符號"$"也可以引用參數,使用"#"最重要的作用就是防止SQL注入

接着看一下Java代碼的寫法:

public Student selectStudentByIdAndName(int studentId, String studentName)
{
    SqlSession ss = ssf.openSession();
    Student student = null;
    try
    {
        student = ss.selectOne("com.xrq.StudentMapper.selectStudentByIdAndName", 
                new Student(studentId, studentName, 0, null));
    }
    finally
    {
        ss.close();
    }
    return student;
}

這里selectOne方法的第二個參數傳入一個具體的Student進去就可以了,運行就不演示了,結果沒有問題。

2、查詢多個結果

上面的演示查詢的是一個結果,對於select來說,重要的當然是查詢多個結果,查詢多個結果有相應的寫法,看一下:

<select id="selectAll" parameterType="int" resultType="Student" flushCache="false" useCache="true"
    timeout="10000" fetchSize="100" statementType="PREPARED" resultSetType="FORWARD_ONLY">
    <![CDATA[
        select * from student where studentId > #{id};
    ]]>
</select>

這里稍微玩了一些花樣,select里面多放了一些屬性,設置了每條語句的作用細節,分別解釋下這些屬性的作用:

  • id----不說了,用來和namespace唯一確定一條引用的SQL語句
  • parameterType----參數類型,如果SQL語句中的動態參數只有一個,這個屬性可有可無
  • resultType----結果類型,注意如果返回結果是集合,應該是集合所包含的類型,而不是集合本身
  • flushCache----將其設置為true,無論語句什么時候被調用,都會導致緩存被清空,默認值為false
  • useCache----將其設置為true,將會導致本條語句的結果被緩存,默認值為true
  • timeout----這個設置驅動程序等待數據庫返回請求結果,並拋出異常事件的最大等待值,默認這個參數是不設置的(即由驅動自行處理)
  • fetchSize----這是設置驅動程序每次批量返回結果的行數,默認不設置(即由驅動自行處理)
  • statementType----STATEMENT、PREPARED或CALLABLE的一種,這會讓MyBatis選擇使用Statement、PreparedStatement或CallableStatement,默認值為PREPARED。這個相信大多數朋友自己寫JDBC的時候也只用過PreparedStatement
  • resultSetType----FORWARD_ONLY、SCROLL_SENSITIVE、SCROLL_INSENSITIVE中的一種,默認不設置(即由驅動自行處理)

xml寫完了,看一下如何寫Java程序,比較簡單,使用selectList方法即可:

public List<Student> selectStudentsById(int studentId)
{
    SqlSession ss = ssf.openSession();
    List<Student> list = null;
    try
    {
        list = ss.selectList("com.xrq.StudentMapper.selectAll", studentId);
    }
    finally
    {
        ss.close();
    }
    return list;
}

同樣,結果也就不演示了,查出來和數據庫內的數據相符。

3、使用resultMap來接收查詢結果

上面使用的是resultType來接收查詢結果,下面來看另外一種方式----使用resultMap,被MyBatis稱為MyBatis中最重要最強大的元素。

上面使用resultType的方式是有前提的,那就是假定列名和Java Bean中的屬性名存在對應關系,如果名稱不對應,也沒關系,可以采用類似下面的方式:

<select id="selectAll" parameterType="int" resultType="Student" flushCache="false" useCache="true"
    timeout="10000" fetchSize="100" statementType="PREPARED" resultSetType="FORWARD_ONLY">
    <![CDATA[
        select student_id as "studentId",student_name as "studentName",student_age as "studentAge",
      student_phone as "studentPhone" from student whehre restudentId > #{id};
]]> </select>

毫無疑問,這樣很繁瑣,我們可以采用resultMap來解決列名不匹配的問題,把2由resultType的形式改成resultMap的形式,Java代碼不需要動:

<resultMap type="Student" id="studentResultMap">
    <id property="studentId" column="studentId" />
    <result property="studentName" column="studentName" />
    <result property="studentAge" column="studentAge" />
    <result property="studentPhone" column="studentPhone" />
</resultMap>

<select id="selectAll" parameterType="int" resultMap="studentResultMap" flushCache="false" useCache="true"
        timeout="10000" fetchSize="100" statementType="PREPARED" resultSetType="FORWARD_ONLY">
    <![CDATA[
        select * from student where studentId > #{id};
    ]]>
</select>

這樣就可以了,注意兩點:

1、resultMap定義中主鍵要使用id

2、resultMap和resultType不可以同時使用

對resultMap有很好的理解的話,許多復雜的映射問題就很好解決了。

 

insert

select看完了,接着看一下插入的方法。首先是student.xml的配置方法,由於插入數據涉及一個主鍵問題,我用的是MySQL,我試了一下使用以下兩種方式都可以:

<insert id="insertOneStudent" parameterType="Student">
    <![CDATA[
        insert into student    values(null, #{studentName}, #{studentAge}, #{studentPhone});
    ]]>    
</insert>
<insert id="insertOneStudent" parameterType="Student" useGeneratedKeys="true" keyProperty="studentId">
    <![CDATA[
        insert into student(studentName, studentAge, studentPhone) 
            values(#{studentName}, #{studentAge}, #{studentPhone});
    ]]>    
</insert>

前一種是MySQL本身的語法,主鍵字段在insert的時候傳入null,后者是MyBatis支持的生成主鍵方式,useGeneratedKeys表示讓數據庫自動生成主鍵,keyProperty表示生成主鍵的列。

Java代碼比較容易:

public void insertOneStudent(String studentName, int studentAge, String studentPhone)
{
    SqlSession ss = ssf.openSession();
    try
    {
        ss.insert("com.xrq.StudentMapper.insertOneStudent", 
            new Student(0, studentName, studentAge, studentPhone));
        ss.commit();
    }
    catch (Exception e)
    {
        ss.rollback();
    }
    finally
    {
        ss.close();
    }
}

還是一樣,insert方法比如傳入Student的實體類,如果insertOneStudent方法要傳入的參數比較多的話,建議不要把每個屬性單獨作為形參,而是直接傳入一個Student對象,這樣也比較符合面向對象的編程思想。

然后還有一個問題,這個我回頭還得再看一下。照理說設置了transactionManager的type為JDBC,對事物的處理應該和底層JDBC是一致的,JDBC默認事物是自動提交的,這里事物卻得手動提交,拋異常了得手動回滾才行。

 

修改、刪除元素

修改和刪除元素比較類似,就看一下student.xml文件怎么寫,Java代碼就不列了,首先是修改元素:

<update id="updateStudentAgeById" parameterType="Student">
    <![CDATA[
        update student set studentAge = #{studentAge} where 
            studentId = #{studentId};
    ]]>    
</update>

接着是刪除元素:

<delete id="deleteStudentById" parameterType="int">
    <![CDATA[
        delete from student where studentId = #{studentId};
    ]]>    
</delete>

這里我又發現一個問題,記錄一下,update的時候Java代碼是這么寫的:

public void updateStudentAgeById(int studentId, int studentAge)
{
    SqlSession ss = ssf.openSession();
    try
    {
        ss.update("com.xrq.StudentMapper.updateStudentAgeById",
                new Student(studentId, null, studentAge, null));
        ss.commit();
    }
    catch (Exception e)
    {
        ss.rollback();
    }
    finally
    {
        ss.close();
    }
}

studentId和studentAge必須是這個順序,互換位置就更新不了學生的年齡了,這個是為什么我還要后面去研究一下,也可能是寫代碼的問題。

 

SQL

SQL可以用來定義可重用的SQL代碼段,可以包含在其他語句中,比如我把上面的插入換一下,先定義一個SQL:

<sql id="insertColumns">
    studentName, studentAge, studentPhone
</sql>

然后在修改一下insert:

<insert id="insertOneStudent" parameterType="Student" useGeneratedKeys="true" keyProperty="studentId">
    insert into student(<include refid="insertColumns" />) 
        values(#{studentName}, #{studentAge}, #{studentPhone});
</insert>

注意這里要把"<![CDATA[ ... ]]>"給去掉,否則"<"和">"就被當成SQL里面的小於和大於了,因此使用SQL的寫法有一定限制,使用前要注意一下避免出錯。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM