前面學習了config.xml,下面就要進入MyBatis的核心SQL映射了,第一篇文章的時候,student.xml里面是這么寫的:
1 2 3 4 5 6 7 8 9 10 11 | <? 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,進行擴展和學習。
Mybatis框架。為什么要使用<![CDATA[ ... ]]>?
上面的配置文件中,大家一定注意到了一個細節,就是SQL語句用<![CDATA[ ... ]]>這對標簽包含起來了,那么為什么要這么做呢?不妨把上面內容稍微修改一下:
1 2 3 4 5 6 7 8 9 | <? 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語句沒有任何含義,只是瞎寫的演示用而已,運行一下看一下結果:
1 2 3 4 5 6 | 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 ... |
MyBatis。后面的異常信息就不列了。按理說很正常的一句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:
1 2 3 4 5 | < select id = "selectStudentByIdAndName" parameterType = "Student" resultType = "Student" > ???? <![CDATA[ ???????? select * from student where studentId = #{studentId} and studentName = #{studentName}; ???? ]]> </ select > |
注意這里的parameter只能是一個實體類,然后參數要和實體類里面定義的一樣,比如studentId、studentName,MyBatis將會自動查找這些屬性,然后將它們的值傳遞到預處理語句的參數中去。
還有一個很重要的地方是,使用參數的時候使用了”#”,另外還有一個符號”$”也可以引用參數,使用”#”最重要的作用就是防止SQL注入。
接著看一下Java代碼的寫法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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來說,重要的當然是查詢多個結果,查詢多個結果有相應的寫法,看一下:
1 2 3 4 5 6 | < 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方法即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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中的屬性名存在對應關系,如果名稱不對應,也沒關系,可以采用類似下面的方式:
1 2 3 4 5 6 7 | < 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代碼不需要動:
1 2 3 4 5 6 7 8 9 10 11 12 13 | < 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,我試了一下使用以下兩種方式都可以:
1 2 3 4 5 | < insert id = "insertOneStudent" parameterType = "Student" > ???? <![CDATA[ ???????? insert into student??? values(null, #{studentName}, #{studentAge}, #{studentPhone}); ???? ]]> ???</ insert > |
1 2 3 4 5 6 | < 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代碼比較容易:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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代碼就不列了,首先是修改元素:
1 2 3 4 5 6 | < update id = "updateStudentAgeById" parameterType = "Student" > ???? <![CDATA[ ???????? update student set studentAge = #{studentAge} where ???????????? studentId = #{studentId}; ???? ]]> ???</ update > |
接著是刪除元素:
1 2 3 4 5 | < delete id = "deleteStudentById" parameterType = "int" > ???? <![CDATA[ ???????? delete from student where studentId = #{studentId}; ???? ]]> ???</ delete > |
這里我又發現一個問題,記錄一下,update的時候Java代碼是這么寫的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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:
1 2 3 | < sql id = "insertColumns" > ???? studentName, studentAge, studentPhone </ sql > |
然后在修改一下insert:
1 2 3 4 | < insert id = "insertOneStudent" parameterType = "Student" useGeneratedKeys = "true" keyProperty = "studentId" > ???? insert into student(< include refid = "insertColumns" />) ???????? values(#{studentName}, #{studentAge}, #{studentPhone}); </ insert > |
注意這里要把”<![CDATA[ ... ]]>”給去掉,否則”<”和”>”就被當成SQL里面的小于和大于了,因此使用SQL的寫法有一定限制,使用前要注意一下避免出錯。