1. 程式人生 > 實用技巧 >筆記8:mybatis

筆記8:mybatis

mybatis筆記

--框架

軟體開發中的一套解決方案,不同的框架解決不同的問題
好處:框架封裝很多細節,使開發者可以使用極簡的方式實現功能,大大提高開發效率

--三層框架

【表現層】	用於展示資料
【業務層】	處理業務需求
【持久層】	資料庫互動

--持久層技術解決方案

【JDBC技術】				Connection	PreparedStatement	ResultSet
【Spring的JdbcTemplate】	spring對jdbc的簡單封裝
【Apache的DBUtils】			和JdbcTemplate很像,是對Jdbc的簡單封裝
以上都不是框架	JDBC是規範	JdbcTemplate和DBUtils都只是工具類

一、概述

---mybatis是持久層框架,java寫的	
---封裝jdbc操作很多細節	
---開發者只需關注sql語句本身無需關注註冊驅動,建立連線等繁雜過程
【ORM】	Object Relational Mappging	物件關係對映
	---把資料庫表和實體類及實體類屬性對應,操作實體 類就實現操作資料庫表
	---需做到:實體類中屬性和資料庫表字段名稱操持一致

二、入門

【環境搭建】
	---建立maven工程並導包
		mybatis-3.4.5.jar
		mysql-connector-java-5.1.6.jar
		log4j-1.2.12.jar
	---建立對應實體類以及dao介面	
	---建立並配置mybatis主配置檔案
		<?xml version="1.0" encoding="UTF-8"?>
		<!DOCTYPE configuration  
		  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  
		  "http://mybatis.org/dtd/mybatis-3-config.dtd">
		 
		<!-- mybatis的主配置檔案 --> 
		<configuration>
			<!-- 配置環境 -->
			<environments default="mysql">
				<!-- 配置mysql環境 -->
				<environment id="mysql">
					<!-- 配置事務型別 -->
					<transactionManager type="JDBC"></transactionManager>
					<!-- 配置資料來源(連線池) -->
					<dataSource type="POOLED">
						<!-- 配置連線資料庫的4個基本資訊 -->
						<property name="driver" value="com.mysql.jdbc.Driver"/>
						<property name="url" value="jdbc:mysql://localhost:3306/jfs_mybatis"/>
						<property name="username" value="root"/>
						<property name="password" value="root"/>
					</dataSource>
				</environment>
			</environments>
			<!-- 指定對映配置檔案的位置,對映配置檔案指的是每個dao獨立的配置檔案 -->
			<mappers>
				<mapper resource="com/xiaoai/dao/IUserDao.xml"/>
			</mappers>
	
	---建立對應介面對映配置檔案
		<?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.xiaoai.dao.IUserDao">
			<!-- 
				配置查詢所有sql
				id值:findAll為介面方法名稱
			 -->
			<select id="findAll" resultType="com.xiaoai.domain.User">
				<!-- 編寫sql語句  -->
				select * from user
			</select>
		 </mapper>
		 
	---注意事項:
		----在MyBatista中把持久層的操作介面名稱和對映檔案叫做:Mapper
			所以:IUserDao 和 IUserMapper其實是相同的東西
		----包:com.xiaoai.dao為三級結構
			目錄:com.xiaoai.dao為一級目錄
		----mybatis的對映配置檔案位置必須和dao介面的包結構相同
		----對映配置檔案的mapper標籤namespace屬性的取值必須是dao介面的全限定類名
		----對映配置檔案的操作(select、update等),id取值必須是介面的方法名
			resultType為獲取資料封裝物件類全限定名
		*遵從三、四、五點後,在開發中無須再寫dao的實現類
		
【入門案例】
	//1--讀取配置檔案
	InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
	//2--建立SqlSessionFactory工廠
	SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
	SqlSessionFactory factory = builder.build(is);
	//3--使用工廠生產SqlSession物件
	SqlSession session = factory.openSession();
	//4--使用SqlSession建立Dao介面代理物件
	IUserDao userDao = session.getMapper(IUserDao.class);
	//5--使用代理物件執行方法
	List<User> list = userDao.findAll();
	for (User user : list) {
		System.out.println(user);
	}
	//6--釋放資源
	session.close();
	is.close();
	
	注意事項:
		---不要忘記在對映配置中告知mybatis對應操作結果要封裝到哪個實體類中
		---配置方式:resultType="com.xiaoai.domain.User"
		
	說明:
		---讀取配置檔案,絕對路徑(d:/xxx/xxx/xx.xml)和相對路徑(src/java/xxx/xx.xml等)不用
			一般用[類載入器]、[ServletContext物件的getRealPath()],因為當編譯後路徑可能會改變
		---建立工廠使用了[構建者模式]		優勢:把物件的建立細節隱藏,使用者直接呼叫方法即可拿到物件
		---生產SqlSession使用了工廠模式		優勢:解耦(降低類之間的依賴關係)
		---建立Dao介面實現類使用代理模式	優勢:不修改原始碼基礎上對已有方法增強
		---實際開發越簡便越好,所以不採用dao實現類方式,但mbatis支援寫dao實現類
		
【註解配置】 
	---介面方法上加對應註解		如:
		@Select("select * from user")
		public List<User> findAll();
	---使用註解配置,不用定義對應介面對映配置檔案。
		在mybatis主配置檔案中<mapper resource="com/xiaoai/dao/IUserDao.xml"/>
		改為<mapper class="com.xiaoai.dao.IUserDao"/>
	
【動態代理】
	---proxy.newProxyInstance(類載入器,代理物件要實現的介面位元組碼陣列,如何代理);
		--類載入器:				使用和被代理物件相同的類載入器
		--代理物件需實現的介面:	和被代理物件實現相同的介面
		--如何代理:				是一個InvocationHandler介面,需寫一個該介面實現類來實現代理物件介面的方法

三、自定義Mybatis

---建立讀取資料流的Resource類讀取配置檔案
---SqlSessionFactoryBuilder即工廠建立,通過XMLConfigBuilder獲取Configuration類獲取對應配置資訊類並建立SqlSessionFactory即工廠
---建立對應類(XMLConfigBuilder)傳遞配置檔案位元組流,
	解析配置檔案並將獲取的配置資訊設定到對應配置類(Configuration、Mapper等,其包含資料庫、介面等配置化資訊)中
---SqlSessionFactory即工廠, 建立SqlSession即建立代理物件的類
---SqlSession即建立代理物件,建立代理物件<T>
---通過代理物件執行相應介面方法 

*資料流(配置原始檔)--》構建者(配置類)--》工廠(執行類)--》代理類(代理物件)--》執行者(執行方法)

四、獲取佔位符引數#{} ${}區別

#{}:<select id="findByName" resultType="com.xiaoai.domain.User" parameterType="string">
		select * from user where username like #{uid}
	</select>select * from user where username like #{uid}
	userDao.findByName("%王%")
	生成:preparing:select * from user where username like ?		用的是PrepatedStatment的引數佔位符sql
	
${}:<select id="findByName" resultType="com.xiaoai.domain.User" parameterType="string">
		select * from user where username like %${value}%
	</select>
	userDao.findByName("王")
	生成:preparing:select * from user where username like %王%	用的是Statment物件的字串拼接sql

五、配置檔案

----主配置檔案
	【properties標籤】	定義屬性
		<!-- 
			配置properties		
			可以在標籤內部配置連線資料庫的資訊,也可以通過屬性引用外部配置檔案資訊
			---resource屬性:
				用於指定配置檔案的位置,是按照類路徑的寫法來寫,並且必須存在於類路徑下
				如果是外部檔案可用resource引入:
					<properties resource="jdbcConfig.properties"></properties>
			---url屬性:
				是要求按照Url的寫法來寫地址
				URL:Uniform Resource Locator 統一資源定位符。它是可以唯一標識一個資源的位置
					寫法:http://localhost:8080/mybatisserver/demo1Servlet
						  協議		主機	埠	URI
				URI:Uniform Resource Identifier	統一資源識別符號	它是在應用中可以唯一定位一個資源的
				
		-->
		<properties>
			<property name="driver" value="com.mysql.jdbc.Driver"/>
			<property name="url" value="jdbc:mysql://localhost:3306/jfs_mybatis"/>
			<property name="username" value="root"/>
			<property name="password" value="root"/>
		</properties>
		<!-- 引用配置的properties	 -->
		<dataSource type="POOLED">
			<!-- 配置連線資料庫的4個基本資訊 -->
			<property name="driver" value="${driver}"/>
			<property name="url" value="${url}"/>
			<property name="username" value="${username}"/>
			<property name="password" value="${password}"/>
		</dataSource>
		
	【typeAliases標籤】	取別名
		 <!-- 使用typeAliases配置別名,它只能配置domain中類的別名 -->
		 <typeAliases>
			<!-- 
				typeAlias用於配置別名,
				type屬性指定實體類全限定類名。
				alias屬性指定別名,當指定別名後則不再區分大小寫
			-->
			<!-- <typeAlias type="com.xiaoai.domain.User" alias="user"/> -->
			<!-- 
				用於指定要配置別名的包,當指定之後,該包下的實體類都會註冊的別名,
				並且類名就是別名 ,不區分大小寫
			-->
			<package name="com.xiaoai.domain"/>
		 </typeAliases>
		
		注意:mappers下的package和上面的有區別:一個指定實體類包,一個指定dao包
			<mappers>
				<!-- 
					用於指定Dao介面所在的包,當指定了之後就不需要
					再寫mapper標籤resource或者class屬性了
				-->
				<package name="com.xiaoai.dao"/>
			</mappers>
	
----對應介面對映檔案
	【selectKey標籤】
		<!-- 
			配置插入操作後,獲取插入資料的id 配置在insert標籤內
			keyProperty	==對應實體類屬性名
			keyColumn	==對應資料庫表字段
			resultType	==返回結果型別
			order		==在插入語句執行之前還是之後執行
		-->
		 <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
			select  last_insert_id();
		 </selectKey>
	
	【ResultMap標籤】	當實體類物件屬性和資料庫列表欄位不對應時可設定欄位的對應關係
		 <!-- 
			配置 查詢結果的列名和實體類的屬性名的對應關係
			type		==實體類所在全限定名
			property	==實體類屬性
			column		==對應的資料庫表字段
		 -->
		 <resultMap id="userMap" type="com.xiaoai.domain.User">
			<!-- 主鍵欄位的對應 -->
			<id property="userId" column="id"/>
			<result property="userName" column="username"/>
			<result property="userAddress" column="address"/>
			<result property="userSex" column="sex"/>
			<result property="userBirthday" column="birthday"/>
		 </resultMap>
		<!-- 定義resultMap後還需要在所需配置中引用	如:查詢所有使用者 -->
		<select id="findAll" resultMap="userMap">
			select * from user
		</select>

六、OGNL表示式 (Object Graphic Navigation Language 物件圖導航語言)

----它是通過物件的取值方法來獲取資料。在寫法上把get給省略及後部分首字母變小寫
	如:獲取使用者名稱稱	類中==user.getUsername();	ognl==user.username
----在mybatis中為什麼能直接寫username而不是user.username呢?因為parameterType引數已經指定了實體類
		<!-- 更新使用者 -->
		<update id="updateUser" parameterType="com.xiaoai.domain.User">
			update user set username=#{username} where id=#{id}
		</update>
----當封裝物件的屬性是另一物件,而查詢的引數在屬性物件有,封裝物件沒有時並且parameterType傳遞的是封裝物件
	則可用"."呼叫鏈式呼叫。
	public class QueryVo {
		private User user;
		public User getUser() {return user;}
		public void setUser(User user) {this.user = user;}
	}
	<!-- 根據queryVo的條件查詢使用者  -->
	<select id="findUserByVo" parameterType="com.xiaoai.domain.QueryVo" resultType="com.xiaoai.domain.User">
		select * from user where username like #{user.username}
	</select>

七、parameterType傳入引數型別

----傳遞簡單型別			只包含一個出則可隨意定義變數名稱
	<!-- 根據id查詢一個使用者 -->
	<select id="findById" resultType="com.xiaoai.domain.User" parameterType="int">
		select * from user where id=#{uid}
	</select>
----傳遞pojo物件			使用對應獲取屬性方法去掉get後部分首字母變小寫
	<!-- 更新使用者 -->
	<update id="updateUser" parameterType="com.xiaoai.domain.User">
		update user set username=#{username} where id=#{id}
	</update>
----傳遞pojo物件的封裝物件	使用"."鏈式呼叫
	<!-- 根據queryVo的條件查詢使用者  -->
	<select id="findUserByVo" parameterType="com.xiaoai.domain.QueryVo" resultType="com.xiaoai.domain.User">
		select * from user where username like #{user.username}
	</select>

八、resultType輸出結果型別

----普通基本型別
----pojo物件
----pojo物件列表
說明:當parameterType傳入pojo物件的屬性與資料庫表字段不一致時的解決方法
	----直接通過sql語句修改查詢欄位別名 如:select id as userId,name as userName from user  等
		方便快速,但由於直接在sql語句修改,當多個sql需要時每個sql都要這樣定義,後續改造時麻煩
	----通過resultMap標籤指定pojo屬性與表字段對應關係
		由於需要多定義一個配置並解析,執行效率慢一些,只需在需要處直接用resultMap="對應配置id"繫結
		修改時只修改resultMap配置內容即可,即後續修改方便

九、連線池 實際開發中都會使用連線池 可以減少獲取

----mybatis連線池提供3種方式配置
	配置位置:主配置檔案<dataSource type="POOLED">標籤,type屬性表示採用何種連線池方式
	type屬性取值:
		【POOLED】		採用傳統javax.sql.DataSource規範中的連線池,mybatis中有針對性規範的實現
			從池中獲取一個連線使用
		【UNPOOLED】	採用傳統獲取連線的方式,雖然也實現javax.sql.DataSource介面,但並沒有使用池的思想
			建立一個連線使用 
		【JNDI】		採用伺服器提供的JNDI技術實現,來獲取DataSource物件,不同伺服器所能拿到的DataSource不一樣
			注意:如果不是web或者Maven的工程,是不能使用的,課程中使用tomcat伺服器,採用連線池就是dbcp連線池

十、mybatis中的事務

通過SqlSession物件的commit()和rollback()方法實現事務提交和回滾
開啟自動提交:sqlSession = factory.openSession(true);//傳入引數true為自動提交事務

十一、動態SQL標籤

----【<if>、<where>標籤】
	<!-- 根據使用者資訊查詢 -->  
	<select id="findByUser" resultType="user" parameterType="user">  
		select * from user
		<where> 
			<if test="username!=null and username != '' "><!-- test="表示式" -->
				and username like #{username}  
			</if> 
			<if test="address != null">
				and address like #{address} 
			</if>
		</where>
	</select> 	
----【<foreach>標籤】
	<!-- 查詢所有使用者在 id 的集合之中 -->
	<select id="findInIds" resultType="user" parameterType="queryvo">
	<!--  select * from user where id in (1,2,3,4,5); --> 
		select * from user
		<where> 
			<if test="ids != null and ids.size() > 0">
				<foreach collection="ids" open="and id in ( " close=")" item="uid"  separator=",">
					<!-- 
						collection==待遍歷的集合
						open==新增語句的開頭  
						close=新增語句的結尾
						item=集合中每個元素的臨時變數
						separator=遍歷分隔符
					-->
					#{uid}     
				</foreach>    
			</if>   
		</where>  
	</select>
----【<sql>標籤】	瞭解內容,抽取重複sql語句
	<!-- 一、抽取重複sql -->
		<sql id="defaultUser">
			select * from user
		</sql>
	<!-- 二、引用抽取的sql -->
		<select id="findInIds" resultType="user" parameterType="queryvo">
			<include refid="defaultUser"></include> 
			<where> 
				<if test="ids != null and ids.size() > 0">
					<foreach collection="ids" open="and id in ( " close=")" item="uid"  separator=",">
						#{uid}
					</foreach>    
				</if>   
			</where>  
		</select>

十二、mybatis中多表查詢

表間關係:	一對一	一對多	多對一	多對多 
----一對一查詢		案例:賬戶和使用者的關係
	----通過封裝一個新類繼承父類並加入所需要的屬性來當resultType的返回的值完成多表查
		public class AccountUser extends Account implements Serializable {    
			private String username;
			private String address;

			public String getUsername() {   return username;  }  
			public void setUsername(String username) {   this.username = username;  }
			public String getAddress() {   return address;  } 
			public void setAddress(String address) {   this.address = address;  }  
			@Override  
			public String toString() {   
				return super.toString() + "   AccountUser [username=" + username + ", address=" + address + "]";  
			}
		} 
		<!-- 配置查詢所有操作-->  
		<select id="findAll" resultType="com.itheima.domain.AccountUser">
			select a.*,u.username,u.address from account a,user u where a.uid =u.id;
		</select>
			
	----定義封裝的account和user的resultMap
		<!-- 一、介面對應配置檔案建立對應關係 -->
		<resultMap type="com.itheima.domain.Account" id="accountMap">
			<id column="aid" property="id"/>
			<result column="uid" property="uid"/>
			<result column="money" property="money"/>
			<!-- 它是用於指定從表方的引用實體屬性的 -->
			<association property="user" javaType="com.itheima.domain.User">
				<id column="id" property="id"/>
				<result column="username" property="username"/>
				<result column="sex" property="sex"/>
				<result column="birthday" property="birthday"/>
				<result column="address" property="address"/>
			</association>
		</resultMap>
		<!-- 二、引用建立的resultMap -->
		<select id="findAll" resultMap="accountMap">
			select u.*,a.id as aid,a.uid,a.money from account a,user u where a.uid =u.id;
		</select>
		//三、獲取到資訊後列印
		System.out.println(account);
		System.out.println(account.user);

----多對一、多對多聯絡的資料都可以通過對應介面配置檔案中<association>標籤建立對應關係進行查詢獲得
	(注:要在查詢對應的實體類中加入泛型為另一所包含資訊的實體類的陣列)

mybatis中的延遲載入

----延遲載入(按需載入、懶載入):	真正使用資料時才發起查詢,不用的時候不查詢。	一對多或多對多使用
		<resultMap type="com.itheima.domain.Account" id="accountMap">
			<id column="aid" property="id"/>
			<result column="uid" property="uid"/>
			<result column="money" property="money"/>
			<!-- 
				select屬性的內容:查詢使用者的唯一標識 
				column屬性指定的內容:使用者根據id查詢時,所需要的引數的值
			-->
			<association property="user" 
						javaType="com.itheima.domain.User" 
						select="com.itheima.dao.IUserDao.findById"
						column="uid"> 
			</association>
		</resultMap>
		<select id="findAll" resultMap="accountMap">
			select * from account
		</select>
		<!-- 主配置檔案配置引數值 -->
		<settings>
			<!-- 開啟mybatis支援延遲載入 -->			
			<setting name="lazyLoadingEnabled" value="true"/>  
			<!-- 允許觸發方法進行立即載入,否則按需載入 -->
			<setting name="aggressiveLazyLoading" value="false"/> 
		</settings>

----立即載入:	不管用不用,只要一呼叫方法,馬上發起查詢	多對一,一對一使用

mybatis中的快取

快取:存在於記憶體中的臨時資料
為什麼用快取:減少資料庫互動次數,提高執行效率
什麼資料可用,什麼不可用:	
	適用:經常查詢、不經常改變、資料正確與否對最終結果影響不大的。
	不適用:經常改變、資料正確與否對最終結果影響很大。
----一級快取:指mybatis中SqlSession物件的緩衝
		當我們執行查詢後,查詢結果會同時存入到SqlSession為為我們提供的一塊區域中。
		該區域結構是一個Map,當我們再次查詢同樣資料,會先去SqlSession中查詢,有直接用
		當SqlSession物件消失時,mybatis一級快取也就消失了
		SqlSession物件.clearCache();//此方法也可清除快取
		當呼叫SqlSession物件修改、新增、刪除、commit()、close()等方法會清空一級快取
----二級快取:指mybatis中SqlSessionFactory物件的快取,
		由同一個SqlSessionFactory物件建立的SqlSession共享其快取
		存放的內容是資料,不是物件,拿到資料後再建立物件並填充資料,
		雖沒有查詢資料庫但是建立了新物件,所以物件不是同一個
		----二級快取使用步驟:
			----讓Mybatis框架支援二級快取(在主配置檔案中配置)
				<settings> 
					<!-- 開啟二級快取的支援 -->  
					<setting name="cacheEnabled" value="true"/> 
				</settings> 
			----讓當前對映檔案支援二級快取(在介面對應配置檔案中配置)
				<mapper namespace="com.itheima.dao.IUserDao">  
					<!-- 開啟二級快取的支援 -->  
					<cache></cache> 
				</mapper>
			----讓當前操作支援二級快取(在select等操作標籤中配置)
				<select id="findById" resultType="user" parameterType="int" useCache="true">  
					select * from user where id = #{uid} 
				</select>

mybatis註解開發

使用註解只是把介面對應的配置檔案註解起來,主配置檔案內容仍存在
----既有註解也有介面對應配置檔案,執行會報錯。在同一dao下用了註解就不能再用xml配置檔案開發。
	----解決:把介面對應配置檔案移動到不相關的目錄去
----實體類屬性與資料庫表字段不一致
	----1-使用別名 如:select username as name from user
	----2-使用註解 即:@Results()完成實體類屬性與欄位對映
		/*查詢所有*/
		@Select(value = "select * from user")
		@Results(id="userMap",value = {//定義id,其他操作可通過@ResultMap()註解引入
				@Result(id=true,column="id",property="userId"),
				@Result(column="username",property="userName"),
				@Result(column="address",property="userAddress"),
				@Result(column="sex",property="userSex"),
				@Result(column="birthday",property="userBirthday"),
		})
		public List<User> findAll();
		----------------------------------------例二
		//其他操作引入類與欄位的對映
		@Select("select * from user where id=#{id}")
		@ResultMap(value = {"userMap"})
		User findById(Integer userid);
----多表查詢
	通過@Results()註解對映對應資訊
	----多對一(mybatis中也稱一對一)
		@Results(id="accountMap",value = {
				@Result(id=true,column="id",property="id"),
				@Result(column="uid",property="uid"),
				@Result(column="money",property="money"),
				//用@Result註解屬性one封裝對應一的屬性(user屬性),即通過select字串找到對應方法及sql語句執行獲得對應資料		
				//fetchType=FetchType.EAGER==什麼載入。EAGER==立即載入  LAZY==延時載入
				@Result(column="user",property="uid",one=@one(select="com.itheima.dao.IUserDao.findById",fetchType=FetchType.EAGER))
		})
	----一對多
		@Results(id="userMap",value = {//定義id,其他操作可通過@ResultMap()註解引入
				@Result(id=true,column="id",property="userId"),
				@Result(column="username",property="userName"),
				@Result(column="address",property="userAddress"),
				@Result(column="sex",property="userSex"),
				@Result(column="birthday",property="userBirthday"),
				//封裝對應多(Account屬性,用many)
				@Result(column="accounts",property="id",many=@many(select="com.itheima.dao.IAccountDao.findAccountByUid",fetchType=FetchType.LAZY))
		})
----註解開發如何使用二級快取
	----在主配置檔案開啟二級快取
		<settings> 
			<!-- 開啟二級快取的支援,不配置也預設開啟 -->  
			<setting name="cacheEnabled" value="true"/> 
		</settings> 
	----介面上面通過@CacheNamespace()註解開啟二級快取
		@CacheNamespace(blocking=true)