前言:
本篇文章對我的學習內容做一個歸納梳理,同時也記錄一些學習過程中遇上的問題及注意事項等等,可以幫助新入門的朋友們消除一些誤區與盲區,希望能幫上一些正在學習的朋友們。在編寫時會引用一些mybatis開發手冊中的內容。本篇中所采用的一些組件版本如下:mybatis—3.4.2、junit—4.12、mysql-connector—5.0.7。
ps:吐槽一下開發手冊,一些地方寫的有點不太清楚,中文版還有一股機翻的味道。。。如果覺得寫的好就點個贊唄,如果覺得寫的不好就提提建議吧!_(:з」∠)_
目錄:
一、快速入門
二、xml配置文件
三、xml映射文件基礎
四、進階應用
正文:
一、快速入門
1.導入mybatis的jar包
要使用 MyBatis,只需將 mybatis-x.x.x.jar 文件置於 classpath 中即可。如果使用 Maven 來構建項目,則需將下面的 dependency 代碼置於 pom.xml 文件中:
1 <dependency> 2 <groupId>org.mybatis</groupId> 3 <artifactId>mybatis</artifactId> 4 <version>x.x.x</version> 5 </dependency
2.從xml配置文件中構建sessionFactory
簡單來說分為兩步:①從配置文件中獲取輸入流。②使用sqlSessionFactoryBuilder.build()方法,將輸入流傳入,獲取sqlSessionFactory。
String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
配置文件:
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE configuration 3 PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-config.dtd"> 5 <configuration> 6 <environments default="development"> 7 <environment id="development"> 8 <transactionManager type="JDBC"/> 9 <dataSource type="POOLED"> 10 <property name="driver" value="${driver}"/> 11 <property name="url" value="${url}"/> 12 <property name="username" value="${username}"/> 13 <property name="password" value="${password}"/> 14 </dataSource> 15 </environment> 16 </environments> 17 <mappers> 18 <mapper resource="org/mybatis/example/BlogMapper.xml"/> 19 </mappers> 20 </configuration>
3.從sessionFactory內獲得session
1 SqlSession session = sqlSessionFactory.openSession();
4.執行已映射SQL語句
映射文件,配置SQL語句
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE mapper 3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 5 <mapper namespace="org.mybatis.example.BlogMapper"> 6 <select id="selectBlog" resultType="Blog"> 7 select * from Blog where id = #{id} 8 </select> 9 </mapper>
執行:采用 "namespace.id(語句)" 的方式進行調用。
1 Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
5.關於作用域與生命周期
sqlSessionFactoryBuilder:一般不長久保持實例,構建完sessionFactory即可銷毀。
sqlSessionFactory:一旦被創建就應該在運行期間一直存在,沒有理由清除或重建。建議使用單例模式來對待它。
sqlSession:因為是線程不安全的,存在線程安全問題,不建議以靜態的方式存在,建議在方法或請求結束后調用close()方法銷毀。
二、xml配置文件
1.enviroments和environment
1 <!--environments:環境集,內部可包含多個環境 2 default:環境的集默認環境,可以自定義 3 通常——developement:開發模式 、work:工作模式--> 4 <environments default="development"> 5 <!--environment:環境,id:指定環境對應的模式,可以自定義--> 6 <environment id="development">
關於default和id的對應關系:在開發手冊中介紹的不夠完善,而我在視頻中學習時只是說,id與default必須一致,詳細也沒有多說。經過我的測試,default表示默認采用的環境屬性,在環境集中必須有至少一個的該類型環境。也就是說在構建sessionFactory時,如果沒有設定指定的環境參數,它就會按照default的參數去尋找一個環境來使用,所以必須得有一個同名的環境存在。而如果在環境集中有多個環境存在,在構建sessionFactory時如果不想使用默認環境,則應指定環境參數。
2.transactionManager
1 <!-- transactionManager:指定事務管理方式,type:事務管理模式,分為JDBC和MANAGED 2 JDBC – 這個配置就是直接使用了 JDBC 的提交和回滾設置,它依賴於從數據源得到的連接來管理事務作用域。 3 MANAGED – 不進行事務管理交由容器來管理事務,如spring,j2ee服務器上下文。 默認情況下會關閉連接, 4 然而一些容器並不希望這樣,因此需要將 closeConnection 屬性設置為 false 來阻止它默認的關閉行為。 --> 5 <transactionManager type="MANAGED"> 6 <property name="closeConnection" value="false"/> 7 </transactionManager>
3.dataSource
1 <!-- dataSource:數據源,配置關於數據庫連接的信息 2 type:連接類型,分為三種 [UNPOOLED|POOLED|JNDI] --> 3 <dataSource type="POOLED"> 4 <!-- 在這里使用的是driver和url,而一些配置文件中則會使用driverClass和jdbcUrl,注意別寫錯 --> 5 <property name="driver" value="${driverClass}" /> 6 <property name="url" value="${jdbcUrl}" /> 7 <property name="username" value="${username}" /> 8 <property name="password" value="${password}" /> 9 </dataSource>
POOLED:"池"類型連接,減少了創建新的連接實例時所必需的初始化和認證時間。是一種使得並發 Web 應用快速響應請求的流行處理方式。
UNPOOLED:按照請求來打開和關閉連接,對於不使用連接池的情況下使用。由於頻繁打開和關閉會導致性能效率較低,對性能要求不高時也可以使用。
JNDI: 這個數據源的實現是為了能在如 EJB 或應用服務器這類容器中使用,容器可以集中或在外部配置數據源,然后放置一個 JNDI 上下文的引用。
4.mappers和mapper
1 <!-- mappers:mapper的集合,mapper標簽都位於此標簽下 --> 2 <mappers> 3 <!-- mapper:注冊映射文件或映射類,也就是告訴mybatis去哪里找映射。有4種注冊方式: --> 4 <!-- 1.使用工程內的路徑結構指定映射文件 --> 5 <mapper resource="cn/edu/mybatis/test1/PeopleMapper.xml" /> 6 <!-- 2.使用全類名指定映射類 --> 7 <mapper class="cn.edu.mybatis.test1.PeopleMapper"/> 8 <!-- 3.指定mybatis的掃描基礎包,將會自動包含該包下所有映射文件及映射文件 (該種方式在typeAliases時會再詳細介紹) --> 9 <package name="cn.edu.mybatis.test1"/> 10 <!-- 4.采用統一資源定位符指定映射文件 (這種方式有點看不懂,測了幾個也測不出來,所以用官方的樣例,望有知道使用方式的朋友能告知一下!) --> 11 <mapper url="file:///var/mappers/BlogMapper.xml"/> 12 </mappers>
5.properties
1 <!-- 定義一個properties,用於引用外部proerties,也可以使用標簽下的<property>來構造一個properties 2 resource:引用文件路徑 --> 3 <properties resource="db.properties"> 4 <!-- 注:當property與外部文件的屬性重名時,結果為外部文件的值,不會產生覆蓋 --> 5 <property name="password" value="124567"/> 6 </properties>
在聲明好properties之后,可以采用 “${鍵}” 的方式來獲取對應的值,提高配置的簡潔及通用性。例:
1 <dataSource type="POOLED"> 2 <property name="driver" value="${driverClass}" /> 3 <property name="url" value="${jdbcUrl}" /> 4 <property name="username" value="${username}" /> 5 <property name="password" value="${password}" /> 6 </dataSource>
6.typeAliases
1 <typeAliases> 2 <!-- 為一個全類名聲明一個別名,以后使用別名便等於使用全類名 3 注:即使聲明了別名,全類名也可以使用,即兩者可以混用,但一般不建議混用--> 4 <typeAlias type="cn.edu.mybatis.test1.People" alias="_People"/> 5 <!-- 將某個包內的全部類設定一個默認別名 --> 6 <package name="cn.edu.mybatis.test1"/> 7 </typeAliases>
關於typeAlias:可以使用@Alias注解來為一個類聲明別名,減少配置文件的使用。如:
1 @Alias("people") 2 public class People {...}
關於package和@Alias:在有@Alias注解的情況下,注解中聲明的別名優先。在開發手冊中寫的默認別名為首字母小寫的類名,但經過測試,默認別名為類名的不區分大小寫形式,如:cn.edu.mybatis.test1.People的默認別名可以為:people、People、pEopLe,但一般還是建議使用首字母小寫的類名。
注:在下面的介紹中將使用別名代替全類名。
7.其它
其它的一些,如:設置(sesttings),對象工廠(objectFactory)等等,由於篇幅過長及不是很常用等一些問題,在這里不進行贅述,有興趣的朋友可以自行去了解。
三、xml映射文件基礎
1.基礎CRUD標簽
表結構:people(id,name,age)
實體類結構:
1 package cn.edu.mybatis.test1; 2 3 public class People { 4 private int id;//標識id,自增長 5 private String name;//姓名 6 private int age;//年齡 7 8 public int getId() { 9 return id; 10 } 11 12 public void setId(int id) { 13 this.id = id; 14 } 15 16 public String getName() { 17 return name; 18 } 19 20 public void setName(String name) { 21 this.name = name; 22 } 23 24 public int getAge() { 25 return age; 26 } 27 28 public void setAge(int age) { 29 this.age = age; 30 } 31 32 public People(String name, int age) { 33 super(); 34 this.name = name; 35 this.age = age; 36 } 37 38 public People() { 39 super(); 40 } 41 42 @Override 43 public String toString() { 44 return "People [id=" + id + ", name=" + name + ", age=" + age + "]"; 45 } 46 47 }
基礎查詢:
1 <!-- select:查詢語句,具體sql語句寫在標簽內 2 parameterType:傳入參數類型,大多數情況可以不寫,mybatis會自動識別 3 resultType:傳出參數類型 4 #{變量名}:表示占位符,替代傳入的參數,只有一個傳入參時可以不使用變量名作為占位符--> 5 <select id="findPeople" parameterType="int" resultType="cn.edu.mybatis.test1.People" > 6 select * from people where id = #{id} 7 </select>
增、刪、改:將標簽改為對應的insert、delete、update即可,傳入傳出參數同查詢,sql語句也是自己自定義。
當傳入參數為一個類的時候,語句的寫法:
1 <update id="insert" parameterType="cn.edu.mybatis.test1.People"> 2 insert into people(name,age) values(#{name},#{age}) 3 </update>
此時占位符將會與類的屬性進行一一對應,因此必須使用與屬性同名的占位符。
注:可以在sql語句中使用${xxx}插入一個動態的字符串實現動態sql,但這種方式會導致sql注入風險,不建議使用。mybatis有提供更好的動態sql方式,在后面會介紹。
2.調用sql語句的三種方式
1 public void select() { 2 /* 調用方法1: */ 3 People people = session.selectOne("cn.edu.mybatis.test1.PeopleMapper.findPeople", 1); 4 System.out.println(people); 5 /* 調用方法2: */ 6 PeopleMapper mapper = session.getMapper(PeopleMapper.class); 7 people = mapper.findPeople(1); 8 System.out.println(people); 9 /* 調用方法3: */ 10 people = mapper.findPeopleById(1); 11 System.out.println(people); 12 }
第一種方式為調用session的方法的方式,傳入 "命名空間.語句id"及參數,進行調用。增刪改同理。
第二種方式聲明了一個接口,在接口內聲明了一個與映射文件中某個標簽的id相同,傳入傳出參數也相同的方法。然后通過session.getMapper()方法獲取該接口的實現,再使用接口進行調用方法。這種方法更加的貼近面向對象的操作方式,同時使用了接口進行了一定程度的隔離,也免除了過長的字符串編寫帶來的一些隱患。是推薦的一種調用方式。
1 public interface PeopleMapper { 2 People findPeople(int id); 3 }
第三種方式看起來和第二種方式區別不大,屬於一個小小的變種。它采用了注解的配置方式,直接將sql語句寫在注解內,減少了配置文件的出現。建議在編寫一些功能邏輯簡單的sql語句時使用這種方式,可以極大的減輕配置文件帶來的負擔。同樣,對應的也有@Insert、@Update、@Delete。
1 @Select("select * from people where id = #{id}") 2 People findPeopleById(int id);
注:在只使用注解的情況下需要在配置文件的mapper中進行注冊該映射類,如果之前注冊過的某個映射文件內的namespace與映射類的全類名相同的話,則不用再次注冊。
3.查詢返回多條結果的寫法、解決字段名和屬性名不同的沖突
多條結果的情況,只需聲明結果集內的類型即可,mybatis會自動封裝。
1 <select id="findAllPeople" resultType="cn.edu.test.People" > 2 select * from people 3 </select>
4.解決字段名和屬性名不同的沖突——resultMap
當數據庫的字段與實體的屬性名不同時,會導致屬性無法獲取的情況發生。如,在數據類型相同,但表為PEOPLE(p_id,p_name,p_age)的情況下,再使用前面用到的查詢語句,將會返回null,這是由於反射機制導致的。解決方法有兩種:第一種,在sql語句中使用as,就可以將查詢結果改名成與屬性名相同,這種方式比較繁瑣笨重。第二種,mybatis提供的更好的解決方式,resultMap。
1 <resultMap type="cn.edu.mybatis.test1.People" id="peopleMap"> 2 <!-- column:數據庫字段名 property:對應屬性名--> 3 <id column="p_id" property="id"/> 4 <result column="p_name" property="name"/> 5 <result column="p_age" property="age"/> 6 </resultMap>
查詢語句中將resultType改為resultMap:
1 <select id="findAllPeople" resultMap="peopleMap" > 2 select * from people 3 </select>
使用這種方式便可以解決沖突的問題。一些更加復雜的映射就不進行贅述了,需要的朋友可以自行查詢。順便一提,通常數據庫列使用大寫單詞命名,單詞間用下划線分隔;而java屬性一般遵循駝峰命名法。在配置文件中的setting標簽內,有一個mapUnderscoreToCamelCase屬性,將其設置為true可以使這兩種命名方式進行自動映射轉換。
四、進階應用
1.1to1關聯映射及1toN關聯映射
預備知識:
在進行正式的學習之前,我們需要了解一些預備知識,以加速加深對mybatis內關聯映射的理解。首先是對於SQL語言,在SQL語言中,如果查詢的信息在多張表內,那么我們一般有兩種方式進行查詢,即子查詢和連接查詢;而在mybatis內也是采用這兩種的類似原理進行關聯映射,手冊中稱之為嵌套查詢和嵌套結果。
稍微了解了映射思想后,我們來梳理下mybatis的查詢流程(自我梳理,如有不對請指正): 
按照這個流程,那么嵌套查詢和嵌套結果的區別就在於"依照規則對復雜類型進行封裝"這步,嵌套查詢在這一步會再調用另一個查詢進行結果的返回 ,嵌套結果則在這一步將連接查詢的結果集按照我們設置的映射規則進行封裝。如果對以上概念理解的差不多了,那么就可以開始我們接下來的映射學習了。
1to1數據庫:employee(id,name,age)、department(id,dname,mid)外鍵為mid依賴於employee的id
1to1實體類:
1 package cn.edu.mybatis.entities; 2 3 public class Department { 4 private int id; //標識id,自增長 5 private String dname; //部門名 6 private Employee manager; //部門管理員,經理 7 8 public int getId() { 9 return id; 10 } 11 12 public void setId(int id) { 13 this.id = id; 14 } 15 16 public String getDname() { 17 return dname; 18 } 19 20 public void setDname(String dname) { 21 this.dname = dname; 22 } 23 24 public Employee getManager() { 25 return manager; 26 } 27 28 public void setManager(Employee manager) { 29 this.manager = manager; 30 } 31 32 public Department() { 33 super(); 34 } 35 36 @Override 37 public String toString() { 38 return "Department [id=" + id + ", dname=" + dname + ", Manager=" + manager + "]"; 39 } 40 41 }
1 package cn.edu.mybatis.entities; 2 3 public class Employee { 4 private int id;//標識id,自增長 5 private String name;//員工名 6 private int age;//員工年齡 7 8 public int getId() { 9 return id; 10 } 11 12 public void setId(int id) { 13 this.id = id; 14 } 15 16 public String getName() { 17 return name; 18 } 19 20 public void setName(String name) { 21 this.name = name; 22 } 23 24 public int getAge() { 25 return age; 26 } 27 28 public void setAge(int age) { 29 this.age = age; 30 } 31 32 public Employee(String name, int age) { 33 super(); 34 this.name = name; 35 this.age = age; 36 } 37 38 public Employee() { 39 super(); 40 } 41 42 @Override 43 public String toString() { 44 return "Employee [id=" + id + ", name=" + name + ", age=" + age + "]"; 45 } 46 47 }
1to1的嵌套查詢式:
1 <select id="findEmployeeById" parameterType="int" resultType="employee"> 2 select * from employee where id = #{id} 3 </select> 4 <select id="findDepartmentById" parameterType="int" resultMap="departmentResult"> 5 select * from department where id = #{id} 6 </select> 7 <resultMap type="department" id="departmentResult"> 8 <association property="manager" column="mid" select="findEmployeeById"></association> 9 </resultMap>
1to1的嵌套結果式:
1 <select id="findDepartmentById2" parameterType="int" resultMap="departmentResult2"> 2 select * from department d,employee e where d.mid=e.id and d.id=#{id} 3 </select> 4 <resultMap type="department" id="departmentResult2"> 5 <id property="id" column="id"/> 6 <result property="dname" column="dname"/> 7 <association property="manager" javaType="employee"> 8 <id property="id" column="mid"/> 9 <result property="name" column="name"/> 10 <result property="age" column="age"/> 11 </association> 12 </resultMap>
1toN的嵌套查詢式:
1 <select id="findClassById" resultMap="classResult"> 2 select * from class where cid=#{id} 3 </select> 4 <!-- 由於是1toN映射,在此處應查詢有外鍵關聯的那個字段 --> 5 <select id="findStudentForClass" resultType="student"> 6 select * from student where c_id=#{id} 7 </select> 8 <!-- 9 ofType:指明多的那方的類型,mybatis會按該類型進行封裝。 10 javaType:可以省略,mybatis會自動轉換。用於指明多的一方的載體類型,如:List、ArrayList等等 11 property、column、select與1to1中的用途一致,不再介紹。 12 --> 13 <resultMap type="class" id="classResult"> 14 <collection property="students" column="cid" ofType="student" select="findStudentForClass"></collection> 15 </resultMap>
1toN的嵌套結果式:
1 <select id="findClassById2" resultMap="classResult2"> 2 select * from class c,student s where s.c_id = c.cid and c.cid = #{id} 3 </select> 4 <!-- 5 將結果映射封裝成Class對象,與以上相同,使用嵌套結果方式時,即使名稱相同必須寫出全部的映射關系。 6 注意:由於該連接查詢會返回多條結果,而方法只返回一個對象,若是映射配置有誤,將會拋出tooManyResultException 7 --> 8 <resultMap type="class" id="classResult2"> 9 <id property="cid" column="cid"/> 10 <result property="cname" column="cname"/> 11 <!-- 必須指定property及ofType,功能與嵌套查詢內的一致,不再作介紹 --> 12 <collection property="students" ofType="student"> 13 <id property="sid" column="sid"/> 14 <result property="sname" column="sname"/> 15 <result property="sage" column="sage"/> 16 </collection> 17 </resultMap>
NtoN映射:
在手冊中並沒有詳細介紹,但根據以往經驗,推測實現方法為:建立第三張關聯關系表,而后分別對這張表進行1toN的映射來實現NtoN的映射。以后有空會附上實現。
2.動態sql
使用的數據庫:employee(id,name,age)
對應的實體類:
1 public class Employee { 2 private int id;//標識id,自增長 3 private String name;//員工名 4 private int age;//員工年齡 5 6 public int getId() { 7 return id; 8 } 9 10 public void setId(int id) { 11 this.id = id; 12 } 13 14 public String getName() { 15 return name; 16 } 17 18 public void setName(String name) { 19 this.name = name; 20 } 21 22 public int getAge() { 23 return age; 24 } 25 26 public void setAge(int age) { 27 this.age = age; 28 } 29 30 public Employee(String name, int age) { 31 super(); 32 this.name = name; 33 this.age = age; 34 } 35 36 public Employee() { 37 super(); 38 } 39 40 @Override 41 public String toString() { 42 return "Employee [id=" + id + ", name=" + name + ", age=" + age + "]"; 43 } 44 45 }
假設現在有一個需求:查詢出employee中年齡在10到50之間的,名字中帶有"o"的對象。
那么對於這個要求,利用我們已學習過的知識可以輕松寫出,首先先建立一個查詢條件類,包含這幾個查詢參數,用於查詢條件的傳遞
1 public class EmployeeCondition { 2 private String name; 3 private int minAge; 4 private int maxAge; 5 6 public String getName() { 7 return name; 8 } 9 10 public void setName(String name) { 11 this.name = name; 12 } 13 14 public Integer getMinAge() { 15 return minAge; 16 } 17 18 public void setMinAge(int minAge) { 19 this.minAge = minAge; 20 } 21 22 public Integer getMaxAge() { 23 return maxAge; 24 } 25 26 public void setMaxAge(int maxAge) { 27 this.maxAge = maxAge; 28 } 29 30 @Override 31 public String toString() { 32 return "DynamicCondition [name=" + name + ", minAge=" + minAge + ", maxAge=" + maxAge + "]"; 33 } 34 35 public EmployeeCondition(String name, int minAge, int maxAge) { 36 super(); 37 this.name = name; 38 this.minAge = minAge; 39 this.maxAge = maxAge; 40 } 41 42 public EmployeeCondition() { 43 super(); 44 } 45 46 }
接下來可以根據我們的已有知識輕松的寫出sql語句
1 <select id="findEmployeeByCondition" resultType="employee"> 2 <!-- 注:此處只使用#{name} 在外部傳遞時使用"%o%" --> 3 select * from employee where name like #{name} and age between #{minAge} and #{maxAge} 4 </select>
當然,這樣寫是可以完成我們的需求。但是在許多實際的情況下,查詢的條件一般都是可以自由組合的,比如X寶,X東等購物網站中,經常有對搜索結果的篩選,在篩選時可以自由的選擇需要的條件及不需要的條件。那么回過頭看我們剛才寫的sql語句,則必須傳遞齊所有參數,不然將會報錯,明顯不適合於實際的情況。那么在此時就會需要用到動態sql的技術,接下來介紹一下動態sql:
動態sql的基本標簽有:if、choose (when, otherwise)、trim (where, set)
if:
使用if標簽,參照需求可以將剛才的sql語句改為:
1 <select id="findEmployeeByCondition2" resultType="employee"> 2 select * from employee where 1=1 3 <if test="name != null"> 4 and name like #{name} 5 </if> 6 <if test="minAge != 0 and maxAge != 0"> 7 and age between #{minAge} and #{maxAge} 8 </if> 9 </select>
但是,如果只使用if標簽,我們必須加上 "where 1=1" 來保證語句在任意的組合情況下都正確。雖然這樣做不是特別復雜,也不會有什么大的影響,但未免有點不太合適。這時就要用上where標簽
where:
where可以幫我們自動添加"where"並且可以自動識別消除語句前的"and ",剛才的sql可以改為以下形式:
1 <select id="findEmployeeByCondition3" resultType="employee"> 2 select * from employee 3 <where> 4 <if test="name != null"> 5 and name like #{name} 6 </if> 7 <if test="minAge != 0 and maxAge != 0"> 8 and age between #{minAge} and #{maxAge} 9 </if> 10 </where> 11 </select>
set:
類似的也有set標簽,用於update語句,用於自動添加"set",並消除語句后的",",比如我們的更新語句就可以改為:
1 <update id="updateEmployee"> 2 update employee 3 <set> 4 <if test="name != null">name=#{name},</if> 5 <if test="age != 0">age=#{age},</if> 6 <trim ></trim> 7 </set> 8 where id = #{id} 9 </update>
trim:
where和set的本質就是trim標簽,可以自動的添加消除前綴及后綴:
1 <!-- 2 <trim 3 prefix:自動添加的前綴 prefixOverrides:自動忽略的前綴(多個忽略采用"|"分隔) 4 suffix:自動添加的后綴 suffixOverrides:自動忽略的后綴(多個忽略采用"|"分隔) 5 </trim> 6 --> 7 <!-- where標簽等價於,注意:and后的空格不可省略 --> 8 <trim prefix="where" prefixOverrides="and |or "></trim> 9 <!-- set標簽等價於 --> 10 <trim prefix="set" suffixOverrides=","></trim>
回到我們一開始的if標簽中,會發現我們的if標簽不支持"else"的功能,那么如果需求中多個條件之間是互斥的情況應該怎么處理呢?mybatis提供了類似於switch的語句,用於完成switch的功能以及else的替代。
choose:修改一下剛才的需求,在查詢時如果不指定年齡區間,則默認查詢出年齡在20到40之間的員工,使用choose標簽可以寫成以下形式
1 <select id="findEmployeeByCondition4" resultType="employee"> 2 select * from employee 3 <where> 4 <if test="name != null"> 5 and name like #{name} 6 </if> 7 <choose> 8 <when test="minAge != 0 and maxAge != 0"> 9 and age between #{minAge} and #{maxAge} 10 </when> 11 <otherwise> 12 and age between 20 and 40 13 </otherwise> 14 </choose> 15 </where> 16 </select>
其中when就相當於case,otherwise則相當於default。利用choose標簽可以實現互斥條件的查詢,也可以用於設置缺省值時的查詢。
3.傳遞多個參數
在前一節中,我們將查詢條件封裝成了一個EmployeeCondition的實體類,但一般情況下極少會這樣封裝,因為這樣會大幅增加代碼的復雜度和冗余度。接下來就來看看怎么使用mybatis進行傳遞多個參數來編寫sql。傳遞多個參數的方法,我們在這里先介紹3種:索引方式、注解聲明方式、map封裝方式
使用的數據庫:employee(id,name,age),id自增長
對應的實體類:
1 public class Employee { 2 private int id;//標識id,自增長 3 private String name;//員工名 4 private int age;//員工年齡 5 6 public int getId() { 7 return id; 8 } 9 10 public void setId(int id) { 11 this.id = id; 12 } 13 14 public String getName() { 15 return name; 16 } 17 18 public void setName(String name) { 19 this.name = name; 20 } 21 22 public int getAge() { 23 return age; 24 } 25 26 public void setAge(int age) { 27 this.age = age; 28 } 29 30 public Employee(String name, int age) { 31 super(); 32 this.name = name; 33 this.age = age; 34 } 35 36 public Employee() { 37 super(); 38 } 39 40 @Override 41 public String toString() { 42 return "Employee [id=" + id + ", name=" + name + ", age=" + age + "]"; 43 } 44 45 }
同樣是前一節的需求,為了縮短篇幅就不采用動態sql及配置文件了,注解可用的查詢語句放到配置文件中也同樣可用。
索引方式:
百度許多,都是寫的老版本的索引方法,如:#{0}、#{1}、...,這樣的索引方式在mybatis3.4之中是不被識別的,經過一番測試后,發現目前版本的支持兩種索引方式:
1 /*#{arg索引}標識,需要注意arg方式索引從0開始*/ 2 @Select("select * from employee where name like #{arg0} and age between #{arg1} and #{arg2}") 3 List<Employee> findEmployeeByCondition(String name,Integer minAge,Integer maxAge); 4 5 /*#{param索引}標識,需要注意param方式索引從1開始*/ 6 @Select("select * from employee where name like #{param1} and age between #{param2} and #{param3}") 7 List<Employee> findEmployeeByCondition2(String name,Integer minAge,Integer maxAge);
索引方式的缺點比較明顯,需要修改占位符的編寫方式,降低配置方面的可讀性。
注解聲明方式:
采用注解聲明方式就是在映射接口中使用@Param來為我們的變量聲明一個名字,使用這種方式占位符按原方式編寫,具有較高通用性,從兩個方面看可讀性都比較高,是我個人比較建議的一種方式。
1 @Select("select * from employee where name like #{name} and age between #{minAge} and #{maxAge}") 2 List<Employee> findEmployeeByCondition3(@Param("name")String name, 3 @Param("minAge")Integer minAge, 4 @Param("maxAge")Integer maxAge);
map封裝方式:
在parameterMap被廢棄的這個版本,也是可以通過java的Map來傳遞多個參數的。其中,key聲明為String,map的key與占位符需對應,有多個參數不同類型時,將map的value聲明為Object即可。
1 @Select("select * from employee where name like #{name} and age between #{minAge} and #{maxAge}") 2 List<Employee> findEmployeeByCondition4(Map<String, Object> map);
1 Map<String, Object> map = new HashMap<>(); 2 map.put("name", "%c%"); 3 map.put("minAge", 10); 4 map.put("maxAge", 50); 5 System.out.println(dao.findEmployeeByCondition4(map));
map方式的缺點也很明顯,單從方法的角度來看,可讀性不高。
傳入多個引用類型:
這種方式不是很常用,但是順便一提吧,有時可能會遇到傳入多個類的情況,那么就可以寫成以下形式:
1 @Select("select * from employee where name like #{ec2.name} and age between #{ec2.minAge} and #{ec2.maxAge}") 2 List<Employee> findEmployeeByCondition5(@Param("ec1")EmployeeCondition ec1, 3 @Param("ec2")EmployeeCondition ec2);
不過,從這個例子可以看出,占位符內是支持變量名.屬性方式調用的,這點與el表達式類似。
4.用List封裝多參數(foreach)——批量操作
使用List需要用到foreach標簽,先來看個例子:
1 <select id="findEmployeeByIds" resultType="employee"> 2 select * from employee where id in 3 <foreach collection="ids" index="index" item="id" open="(" separator="," close=")"> 4 #{id} 5 </foreach> 6 </select>
以上功能為根據id集來查詢獲得對象集,等價於sql語句:select * from employee where id in (xx,xx,...),接下來詳細介紹下foreach標簽:
foreach:用於集合迭代,操作與JSTL中的foreach標簽類似
collection:指定要迭代集合的名稱,自定義名稱需要使用@param注解標明,不使用注解的情況下,List默認名為list或collection,數組默認名為array
index:當前迭代次數,用於判斷語句中使用,名稱可自定義(可選)
item:當前迭代的對象,名稱可自定義
open:整個集合開始時在左邊添加的字符(可選)
separator:每次迭代結束后在右邊添加的字符,即分隔符(可選)
close:整個集合結束時在右邊添加的字符(可選)
有了foreach標簽的支撐,就可以實現一些批量操作了,比如常用的批量插入及批量更新。
批量插入:
1 <insert id="addEmployeeByList"> 2 insert into employee(name,age) 3 values 4 <foreach collection="employees" item="employee" separator=","> 5 (#{employee.name},#{employee.age}) 6 </foreach> 7 </insert>
批量更新:
1 <update id="updateEmployeeByList"> 2 <foreach collection="employees" item="employee" separator=";"> 3 update employee 4 <set> 5 name=#{employee.name}, 6 age=#{employee.age} 7 </set> 8 where id=#{employee.id} 9 </foreach> 10 </update>
當使用這種方式的批量更新時,如果數據庫是mysql則需要添加allowMultiQueries=true參數,如:
jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
此外,foreach是可以嵌套使用的,若是類中有數組時,可以嵌套使用。
5.mybatis緩存機制
正如大多數持久層框架一樣,mybatis同樣提供了一級緩存和二級緩存的支持
一級緩存:基於PerpetualCache的HashMap本地緩存,其存儲作用域為Session,當Session被flush或close之后,該Session中的所有Cache就將清空。
二級緩存:與一級緩存機制相同,默認也是采用PerpetualCache,HashMap存儲,不同在於其存儲作用域為Mapper(namespace),並且自定義存儲源,如Ehcache。
緩存數據更新機制:當某一個作用域(一級緩存Session/二級緩存Namespaces)的進行了增/刪/改操作后,默認該作用域下所有的select緩存將被clear。
6.mybatis調用存儲過程並返回值
首先先看下parameterMap,在很多描述中調用存儲過程需要用到。但是,在官方手冊中:
parameterMap– 已廢棄!老式風格的參數映射。內聯參數是首選,這個元素可能在將來被移除,這里不會記錄。
廢棄了,沒辦法。搜索了許久,找到現版本的用法,把parameter的參數寫在占位符內傳遞,如:#{eid,mode=IN,jdbcType=INTEGER}。知道了替代的方式之后,就可以使用mybatis調用存儲過程了。存儲過程:
1 CREATE PROCEDURE test(IN eid INTEGER,OUT ename VARCHAR(50)) 2 BEGIN 3 SELECT name into ename 4 from employee 5 where id = eid; 6 END
調用:
1 <select id="callProcedure" statementType="CALLABLE"> 2 call test(#{eid,mode=IN,jdbcType=INTEGER},#{ename,mode=OUT,jdbcType=VARCHAR}) 3 </select>
1 public interface ProcedureDao { 2 void callProcedure(Map<String, Object> map); 3 }
1 String ename = null; 2 Map<String, Object> map = new HashMap<>(); 3 map.put("eid", 1); 4 map.put("ename", ename); 5 dao.callProcedure(map); 6 System.out.println(map.get("ename"));//有值 7 System.out.println(ename);//null
注意事項:
-
- statementType設置為CALLABLE才能識別調用call語句。
- 只能采用map封裝的方式進行傳遞,才能獲取到OUT出來的值,索引方式和注解方式不能獲取。
- 占位符內設置屬性時采用的是enum類型,故必須大寫。
獲取函數的返回值,類似這樣:
1 <select id="callProcedure" statementType="CALLABLE"> 2 {#{d,mode=OUT,jdbcType=INTEGER}=call test(#{eid,mode=IN,jdbcType=INTEGER},#{ename,mode=OUT,jdbcType=VARCHAR})} 3 </select>
如果要接受結果集,采用resultMap封裝成Map傳出即可。
7.與spring整合
在實際應用中,mybatis常常需要和spring一起使用,那么mybatis與spring整合是整合什么呢?mybatis與spring整合主要是以下幾點:
-
- 將事務管理交給spring。
- 將sqlSessionFactory及sqlSession交由springIOC容器管理。
- 將Mapper接口及配置文件交由springIOC容器管理。
環境准備:除去spring的基礎需要包以外,還需要mybatis-spring-1.3.1.jar。此處spring版本為4.3.3,故采用1.3.1版本文件。
配置文件:
其配置主要在spring中,一個的簡單配置文件樣例如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" 4 xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 6 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd 7 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd 8 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd"> 9 10 <!-- 1.配置數據源:DriverManagerDataSource --> 11 <bean id="dataSource" 12 class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 13 <property name="url" value="jdbc:mysql://localhost:3306/mybatis"></property> 14 <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> 15 <property name="username" value="root"></property> 16 <property name="password" value="root"></property> 17 </bean> 18 <!-- 2.創建sqlSessionFactory:SqlSessionFactoryBean --> 19 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 20 <!-- 2-1.為sessionFactory附上數據源 --> 21 <property name="dataSource" ref="dataSource"></property> 22 <!-- 2-2.設置掃描別名的包,一般設置實體類所在包 等價於原先package標簽 --> 23 <property name="typeAliasesPackage" value="cn.edu.mybatis.entities"></property> 24 </bean> 25 <!-- 3.配置自動掃描mybatis接口/配置文件的類:MapperScannerConfigurer --> 26 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 27 <!-- 3-1.配置對應的sqlSessionFactory --> 28 <property name="sqlSessionFactory" ref="sqlSessionFactory"></property> 29 <!-- 3-2.配置掃描基礎包 --> 30 <property name="basePackage" value="cn.edu.mybatis.dao"></property> 31 </bean> 32 <!-- 4.配置事務管理器:DataSourceTransactionManager --> 33 <bean id="transactionManager" 34 class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 35 <!-- 需要設置數據源 --> 36 <property name="dataSource" ref="dataSource"></property> 37 </bean> 38 <!-- 5.配置聲明式事務 --> 39 <tx:annotation-driven transaction-manager="transactionManager" /> 40 </beans>
配置完成后就可以使用@Autowired和@Resource對Mapper接口進行注入使用了,事務也交由spring進行管理。
