前言
前面學習了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的寫法有一定限制,使用前要注意一下避免出錯。