Mybatis基礎知識

1、hello world

    在線參考文檔:http://mybatis.github.io/mybatis-3/zh/index.htmlhtml

    首先咱們定義一個需求:根據id查詢用戶。
java

    一、編寫必須的配置文件。

    開發mybatis入門程序須要編寫兩個配置文件,全局配置文件(SqlMapperConfig.xml)和sql語句配置文件(xxxMapper.xml)。
mysql

    a、全局配置文件(SqlMapConfig.xml)git

<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<environments default="development">
		<environment id="development">
		<!-- 使用jdbc事務管理-->
			<transactionManager type="JDBC" />
		<!-- 數據源-->
		        <!-- POOLED參數表示使用鏈接池,mybatis框架自帶鏈接池 -->
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver"/>
				<property name="url" value="jdbc:mysql://localhost:3306/mybatishelloworld?useUnicode=true&amp;characterEncoding=UTF-8"/>
				<property name="username" value="root"/>
				<property name="password" value="root"/>
			</dataSource>
		</environment>
	</environments>
	
	<!--  加載mapper.xml -->
	<mappers>
		<mapper resource="mapper/userMapper.xml"/>
	</mappers>
</configuration>

    如上,在sqlMapConfig.xml的configuration元素中編寫配置信息,這裏須要注意加載xxxMapper.xml 。
github

    b、sql映射文件(userMapper.xml)spring

<?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="ff.ing.mapper.userMapper">

	<!-- 根據id查詢用戶 -->
	<select id="findUserById" parameterType="int" resultType="ff.ing.po.User">
		SELECT * FROM user WHERE id = #{id}
	</select>
</mapper>

    如上,在userMapper.xml的mapper元素中編寫sql語句,namespace屬性定義了一個命名空間,select元素定義了一個select查詢語句,id爲語句的惟一標識,parameterType爲輸入映射類型,resultType爲輸出映射類型,#{}可理解爲佔位符,功能同預編譯裏的 '?'。sql

    二、編寫代碼。

    在上面的userMapper.xml中的select語句有三個屬性,id、parameterType和resultType,id爲這個語句的惟一標識,parameterType爲輸入映射,resultType爲輸出映射。在這個語句中parameterType類型爲int,resultType類型爲一個pojo類型,因此咱們須要建立這個pojo類型。代碼以下:shell

public class User {
	
	private int id;
	private String username;// 用戶姓名
	private String sex;// 性別
	private Date birthday;// 生日
	private String address;// 地址
	
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	public Date getBirthday() {
		return birthday;
	}
	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	
	@Override
	public String toString() {
		return "User [id=" + id + ", username=" + username + ", sex=" + sex + ", birthday=" + birthday + ", address="
				+ address + "]";
	}
}

    接下來編寫測試代碼。數據庫

public class FirstDemo {
	
	private SqlSessionFactory sqlSessionFactory = null;
	
	// 執行該類任何方法前調用這個方法
	// 加載全局配置文件
	@Before
	public void init() throws IOException {
		
		String config = "SqlMapConfig.xml";
		InputStream inputStream = Resources.getResourceAsStream(config);
		
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	}
	
	// 根據id查詢用戶
	@Test
	public void findUserById() {
		
		// 獲取SqlSession對象
		SqlSession sqlSession = sqlSessionFactory.openSession();
		User user = null;
		
		try {
			// 執行SqlSession對象中的selectOne方法執行sql語句
			// 參數1爲sql語句惟一標識(namespace + id),參數2爲輸入映射
			user = sqlSession.selectOne("ff.ing.mapper.userMapper.findUserById", 1);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			sqlSession.close();
		}
		System.out.println(user);
	}
}

    測試結果apache

    

    到這裏,一個mybatis入門程序就寫完了。

    附上項目目錄結構

    

2、是什麼

MyBatis 本是apache的一個開源項目iBatis, 2010年這個項目由apache software foundation 遷移到了google code,而且更名爲MyBatis,實質上Mybatis對ibatis進行一些改進。 目前mybatis在github上託管。git(分佈式版本控制,當前比較流程)

MyBatis是一個優秀的持久層框架,它對jdbc的操做數據庫的過程進行封裝,使開發者只須要關注 SQL 自己,而不須要花費精力去處理例如註冊驅動、建立connection、建立statement、手動設置參數、結果集檢索等jdbc繁雜的過程代碼。

Mybatis經過xml或註解的方式將要執行的各類statement(statement、preparedStatemnt、CallableStatement)配置起來,並經過java對象和statement中的sql進行映射生成最終執行的sql語句,最後由mybatis框架執行sql並將結果映射成java對象並返回。

3、爲何要用與適用場景

    1 傳統jdbc開發方式缺陷

    a 數據庫鏈接建立和關閉頻繁,形成數據庫資源浪費,影響系統效率。改進:使用數據庫鏈接池。

    b 代碼中sql語句硬編碼,若是需求變動須要修改sql,就必須修改java代碼,必須從新編譯,使系統不易維護。改進:將sql語句統一配置在文件中,修改sql語句時不須要修改java代碼。

    c dao層開發中經過preparedStatement向佔位符設置參數存在硬編碼(參數位置,參數),使系統不易維護。改進:將sql中的佔位符和對應的參數類型配置在文件中,提供將參數自動注入到sql語句中的功能(輸入映射)。

    d dao層開發中遍歷查詢結果集存在硬編碼(結果集列名硬編碼)。改進:實現sql查詢結果集向java對象的自動封裝功能。(輸出映射)    

    總結一下,用mybatis開發項目具備高靈活性,易於維護的優勢,mybatis還支持主流數據庫鏈接池,具備動態sql的特性,能夠輕鬆的根據不一樣條件拼接sql,因此樂意用它。

    2 適用場景

    mybatis適用於開發需求變動頻繁的系統,好比互聯網項目;傳統jdbc開發方式或框架,如hibernate適用於需求固定,對象數據模型穩定的項目,好比企業OA系統。

4、架構

    

5、怎麼用

一、基礎知識點

a、全局配置文件

    mybatis全局配置文件名字不固定,上邊入門demo裏使用SqlMapConfig.xml命名。它的有用是配置mybatis的運行環境,如數據源、事物等,以及加載sql映射配置文件。配置文件頂層結構以下:

    • configuration 配置

      • properties 屬性

      • settings 設置

      • typeAliases 類型命名

      • typeHandlers 類型處理器

      • objectFactory 對象工廠

      • plugins 插件

      • environments 環境

        • environment 環境變量

        • transactionManager 事務管理器

        • dataSource 數據源

      • databaseIdProvider 數據庫廠商標識

      • mappers 映射器

    properties配置了一些可動態替換的參數,如數據庫信息。

    settings中包含不少子節點,定義了mybatis的行爲。

    typeAliases定義類的別名,做用是簡化sql映射文件的編寫。mybatis已經爲經常使用java類型定義了別名。

    typeHandlers做用是實現數據庫數據類型和java類型間的轉換,它支撐着mybatis的輸入和輸出映射。可自定義類型

    objectFactory用於建立查詢結果集映射的對象實例。

    environments配置環境,有三個子節點,environment節點定義環境id,用於惟一標識該環境;transactionManager設置事物管理類型;dataSource配置數據源。

    mappers定義須要加載的sql映射文件。

    詳細配置參考在線參考文檔XML配置一節。


b、sql映射文件

    sql映射文件一樣沒有固定命名,但建議使用xxxMapper.xml形式命名。它的做用配置映射的sql語句,達到省略jdbc代碼,使開發人員可以專一sql語句自己。映射文件頂層結構以下:

            cache – 給定命名空間的緩存配置。

            cache-ref – 其餘命名空間緩存配置的引用。

            resultMap – 是最複雜也是最強大的元素,用來描述如何從數據庫結果集中來加載對象。

            sql – 可被其餘語句引用的可重用語句塊。

            insert – 映射插入語句

            update – 映射更新語句

            delete – 映射刪除語句

            select – 映射查詢語句

    詳細配置信息參考在線參考文檔XML映射文件一節。

c 、SqlSessionFactoryBuilder、SqlSessionFactory與SqlSession

    經過SqlSessionFactoryBuilder對象的build()方法加載全局配置文件建立SqlSessionFactory對象,經過SqlSessionFactory對象的openSession()方法建立SqlSession對象,SqlSession對象操做數據庫(實際由Executor執行sql語句)。執行insert、update、delete、select後須要執行SqlSession對象中的commit()方法,最後不要忘了關閉SqlSession。

    三個對象的API參考在線參考文檔中的Java API一節。

d、輸入映射、輸出映射

    輸入映射

    sql映射語句中經過配置parameterType屬性定義輸入映射(parameterMap已經棄用),能夠是簡單類型(int、byte、char、double……)也能夠是對象(java內置對象或者自定義對象),用於支撐#{}和${}取值功能,當類型爲對象時經過這個對象的getter函數獲取值。

    輸出映射

    sql映射語句中經過配置resultType或resultMap屬性定義輸出映射,通常是對象。

    resultType:mybatis簡單的將結果集封裝成resultType屬性裏配置的對象,若是類型是自定義對象則要求屬性名和結果集字段名一致,不然報錯。

    resultMap:mybatis經過配置resultMap將結果集字段映射成自定義名稱,方便處理多表查詢結果集字段衝突的狀況和某些需求下的複雜查詢(一對多,多對多)。

    一樣的,細節參考在線參考文檔中XML映射文件一節下的參數和結果集

e、${}與#{}

    #{}與${}都是mybatis中的取值表達式,但它們的區別還挺大。

    #{}表示一個佔位符,它在取值時會自動進行java類型和jdbc類型的轉換,這樣開發人員不須要考慮參數類型在sql語句中書寫的規則,好比:select * from user where name = #{user.name},在這裏user.name是一個字符串,假設值爲"jack",mybatis最終拼接好的sql會是這樣,select * from user where name = 'jack'。

    ${}表示簡單的sql拼接,並不會進行類型轉換,好比上面的sql語句若是改用${}得這樣寫:select * from user where name = '${user.name}'。

    #{}相比於${}還有個很是大的優勢——能夠防止sql注入,由於#{}效果同jdbc預編譯中的'?'符號,而${}只是簡單的字符串拼接。

    注意:當parameterType爲String或者其它java內置對象時,使用#{_parameter}或${_parameter}獲取值。

f、動態sql

    mybatis動態sql特性支持根據不一樣條件動態的拼接sql,for each遍歷parameterType中的集合類型以及重複使用sql片斷等等。

    根據不一樣條件動態拼接sql案例:

<select id="findUserList" parameterType="ff.domain.User" resultType="ff.domain.User">
    SELECT id, username, birthday, sex FROM user
    <where>
        <if test="null != User>
            <if test="null != User.username and '' != User.username">
                and username like '%${User.username}%'
            </if>
            <if test="null != User.sex and '' != User.sex">
                and sex = #{User.sex}
            </if>
        <if>
    </where>
</select>

    mybatis會自動忽略where節點裏的第一個and關鍵字。

    將上面的sql映射用sql片斷實現,以下:

<sql id="user_query_where">
    <if test="null != User>
        <if test="null != User.username and '' != User.username">
            and username like '%${User.username}%'
        </if>
        <if test="null != User.sex and '' != User.sex">
            and sex = #{User.sex}
        </if>
    <if>
</sql>


<select id="findUserList" parameterType="ff.domain.User" resultType="ff.domain.User">
    SELECT id, username, birthday, sex FROM user
    <where>
      <include refid="user_query_where"></include>
    </where>
</select>

    先在mapper.xml文件中定義sql片斷,而後能夠在sql映射語句中使用include元素引用。

    for each元素用於遍歷parameterType中的集合類型,這裏不作演示。

    一樣的,動態sql特性其它功能以及使用細節參考在線參考文檔動態sql一節。

g、主鍵返回

    針對mysql數據庫

    定義一個需求:添加一個用戶,並返回該用戶的主鍵。

    方法一:若是是自增主鍵,在執行插入用戶的sql語句後使用LAST_INSERT_ID()函數獲取最後插入記錄的主鍵值

<insert id="insertUser" parameterType="ff.ing.po.User">
    <!-- 插入後查詢最後插入記錄的id並封裝進User實例,解決返回主鍵需求 -->
    <selectKey keyProperty="id" order="AFTER" resultType="int">
        SELECT LAST_INSERT_ID()
    </selectKey>
    INSERT INTO user(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address})
</insert>

    經過<selectKey>元素能夠定義一個insert映射語句的執行先後進行的一些額外操做,如在上面的語句中的selectKey元素將會首先執行,這是selectKey元素order屬性的功勞,AFTER值定義這個selectKey元素中sql語句在insert語句執行後執行,keyProperty定義將查詢結果封裝到輸入映射的哪個屬性中,resultType定義了查詢結果的類型。這條語實如今執行insert語句後執行查詢最後插入記錄主鍵值語句,而後將查詢結果封裝到輸入映射(ff.ing.po.User)的id屬性中。

sqlSession.insert("ff.ing.mapper.userMapper.insertUser", userIn);
sqlSession.commit();
System.out.println(userIn.getId());

    在測試中執行上面代碼後,userIn中的id屬性自動被設置成user表中最後插入的記錄的主鍵值。

    方法二:若是不是自增主鍵,可以使用mysql內置的UUID()函數實現,在執行插入用戶的sql語句前生成UUID並封裝到輸入映射,而後再執行插入語句

<insert id="insertUserByUUID" parameterType="ff.ing.po.User1">
    <!-- 插入前先生成UUID,將UUID封裝到輸入映射中再執行插入語句 -->
    <selectKey keyProperty="id" order="BEFORE" resultType="String">
	SELECT UUID()
    </selectKey>
    INSERT INTO user1(id,username,birthday,sex,address) VALUES(#{id},#{username},#{birthday},#{sex},#{address})
</insert>

    order屬性值爲BEFORE,使執行insert前先執行selectKey元素裏生成UUID的sql語句並封裝到輸入映射中,而後再執行insert語句

sqlSession.insert("ff.ing.mapper.userMapper.insertUserByUUID", userIn);
sqlSession.commit();
System.out.println(userIn.getId());

    測試中執行上面代碼後,userIn的id屬性值爲UUID()產生的32數列

二、mybatis原始dao開發方式與mapper代理開發方式

    首先定義一個需求:根據id查詢用戶信息

    無論是原始dao開發方式或者mapper代理開發方式都須要先編寫全局配置文件和sql映射文件,這裏同hello world一致。

    原始dao開發方式

    第一步寫接口。

public interface UserDao {
	
	public User findUserById(int id) throws Exception;
}

    第二步寫實現類

public class UserDaoImp implements UserDao{
	
	private SqlSessionFactory sqlSessionFactory = null;
	
	public UserDaoImp(SqlSessionFactory sqlSessionFactory) {
		
		this.sqlSessionFactory = sqlSessionFactory;
	}

	@Override
	public User findUserById(int id) throws Exception {
		
		SqlSession sqlSession = sqlSessionFactory.openSession();
		User user = sqlSession.selectOne("ff.ing.mapper.userMapper.findUserById", 1);
		sqlSession.close();
		return user;
	}

}

    由於SqlSession類是線程不安全的,因此將其定義在方法體內

    第三步測試

@Test
public void testFindUserById() throws Exception {
		
    User user = new UserDaoImp(sqlSessionFactory).findUserById(1);
    System.out.println(user);
}

    測試結果

    

    mapper代理開發方式

    開發規範:

        a. sql映射文件中mapper元素namespace屬性值爲mapper接口的全限定名。

        b. sql映射文件中sql語句元素的id屬性值爲mapper接口裏的方法名,resultType屬性值或resultMap元素type屬性值爲方法返回類型,parameterType屬性值爲方法參數類型。

    第一步修改userMapper.xml文件,使其符合開發規範   

    因爲規範a,咱們將sql映射文件中mapper元素namespace屬性值改爲ff.ing.mapper.Usermapper

    第二步根據userMapper.xml建立接口

public interface UserMapper {
	
	public User findUserById(int id) throws Exception;
}

    第三步測試

@Test
public void testFindUserById() throws Exception {
		
	SqlSession sqlSession = sqlSessionFactory.openSession();
		
	// 獲取代理對象,實質是mybatis幫咱們建立接口實現類
	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
	User user = userMapper.findUserById(1);
	sqlSession.close();
	System.out.println(user);
}

    測試結果

    

    能夠看出,mybatis的mapper開發方式省略了重複的SqlSession執行sql語句操做過程,同時還避免因傳統dao開發方式中dao實現類輸入映射的硬編碼問題,提升了可維護性(在傳統dao開發方式中,若是輸入映射類型變了,就必須同時修改sql映射文件裏的parameterType屬性值和dao層實現類代碼,而用mapper代理開發方式只須要修改sql映射文件裏的parameterType屬性值)

    注:在寫博客過程當中忘了修改userMapper.xml中mapper元素的namespace,遇到這樣的報錯

    

    緣由是規範a規定userMapper.xml中mapper元素的namespace屬性值爲接口全限定名(未修改時是userMapper),而建立接口時定義接口名爲UserMapper,mybatis找不到名爲userMapper的接口因此報錯。

三、mybatis簡化配置與hello world優化

    mybatis提供一套簡化開發的規範(包括mapper代理開發規範),這一節記錄如何簡化開發。一樣的,定義一個需求:根據id查詢用戶信息。

    第一步簡化sql映射文件配置。在全局配置文件中定義別名能夠避免書寫類的全限定名,這裏使用掃描包方式定義別名

<!-- 定義別名 -->
<typeAliases>
    <!-- 掃描包方式定義別名,每一個類的類名就是其別名,mybatis會自動將類名看成別名並映射類的全限定名。配置後在輸入、輸出映射裏可使用別名替換類的全限定名 -->
    <package name="ff.ing.po"/>
</typeAliases>

    第二步全局配置文件mappers元素裏改用掃描包的方式替換配置子mapper元素方式,這樣避免配置每一個mapper的重複操做。

    注意:這裏有個規範,要求mapper.xml文件與其對應的mapper.java接口同名且在同一個包中

<!--  加載mapper.xml -->
<mappers>
    <!-- 使用掃描包方式加載mapper,mybatis會自動加載這個包下全部mapper文件 -->
    <package name="ff.ing.mapper"/>
</mappers>

    到這裏簡化開發的配置文件修改工做已經完成,不過還能夠做一個小改進,用配置文件替換全局配置中數據源屬性值,增長一些靈活性。

    在config源文件夾中建立db.properties配置文件,配置內容以下:

# 數據源配置
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatishelloworld?useUnicode=true&amp;characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root

    接着在全局配置文件中添加properties配置

<!-- 加載數據庫配置文件 -->
	<properties resource="db.properties"></properties>

     而後修改全局配置文件的environments子元素environment的子元素dataSource裏的屬性值。

<!-- 數據庫鏈接池-->
<dataSource type="POOLED">
    <property name="driver" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</dataSource>

    如今全局配置文件修改完成,下面是修改後的全局配置文件。

<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	
	<!-- 加載數據源配置文件 -->
	<properties resource="db.properties"></properties>
	
	<!-- 定義別名 -->
	<typeAliases>
		<!-- 掃描包方式定義別名,別名就是類名。配置後在輸入、輸出映射裏可使用別名替換類的全限定名 -->
		<package name="ff.ing.po"/>
	</typeAliases>
	
	<environments default="development">
		<environment id="development">
		<!-- 使用jdbc事務管理-->
			<transactionManager type="JDBC" />
		<!-- 數據庫鏈接池-->
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driver}"/>
				<property name="url" value="${jdbc.url}"/>
				<property name="username" value="${jdbc.username}"/>
				<property name="password" value="${jdbc.password}"/>
			</dataSource>
		</environment>
	</environments>
	
	<!--  加載mapper.xml -->
	<mappers>
		<!-- 使用掃描包方式加載mapper,mybatis會自動加載這個包下全部mapper文件 -->
		<package name="ff.ing.mapper"/>
	</mappers>
</configuration>

    第三步修改sql映射文件,使用別名替換類全限定名,下面是修改後的文件。

<?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="ff.ing.mapper.UserMapper">

	<!-- 根據id查詢用戶 -->
	<select id="findUserById" parameterType="int" resultType="User">
		SELECT * FROM user WHERE id = #{id}
	</select>
		
</mapper>

   到這裏,簡化開發大功告成。

四、resultMap和resultType應用在複雜查詢時的區別

    定一個需求:查詢訂單關聯查詢訂單明細和用戶名、用戶性別

    數據模型說明:orders表中存儲用戶訂單信息,orderdetail表中存儲訂單明細信息,user表中存儲用戶信息,user表和orders表一對多關係,orders表和orderdetail表一對多關係。

    先把sql語句寫出來:

            SELECT

                orders.*, user.username,

                user.sex,

                orderdetail.id orderdetail_id,

                orderdetail.items_num,

                orderdetail.items_id

            FROM

                orders,

                orderdetail,

                user

            WHERE

                orders.user_id = user.id

                AND orders.id = orderdetail.orders_id;

    使用resultType開發

    建立Order對象,而後建立OrderQuery對象繼承Order對象,而後添加查詢結果集中其它字段,確保OrderQuery對象屬性與結果集字段名一一對應。

public class OrderQuery extends Order {
	
	// 用戶信息
	private String username;
	private String sex;
	
	// 訂單明細信息
	private int orderdetail_id;
	private int items_id;
	private int items_num;

    配置sql映射文件

<?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="ff.ing.mapper.OrderMapper">

	<!-- 一對多查詢, 查詢訂單信息關聯查詢訂單明細和用戶名、用戶性別 -->
	<select id="findOrderOrderdetailUser" resultType="OrderQuery">
		SELECT
			orders.*, user.username,
			user.sex,
			orderdetail.id orderdetail_id,
			orderdetail.items_num,
			orderdetail.items_id
		FROM
			orders,
			orderdetail,
			user
		WHERE
			orders.user_id = user.id
			AND orders.id = orderdetail.orders_id;
	</select>

</mapper>

    根據sql映射文件建立mapper接口,建立測試類進行測試

    使用resultMap開發

    在Order類中關聯User、OrderDetail

public class Order {
	
	private int id;
	private int user_id;
	private String number;
	private Date createtime;
	private String note;
	
	// 關聯用戶
	private User user;
	
	// 關聯訂單明細
	private List<OrderDetail> orderDetails;

    配置sql映射文件

<?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="ff.ing.mapper.OrderMapper">

	<!-- 訂單關聯訂單明細和用戶名、用戶性別 -->
	<!-- resultMap做用的將結果集的字段名映射到pojo -->
	<!-- type定義將結果集映射到哪一個pojo id是resultMap的惟一標識 -->
	<resultMap type="Order" id="orderOrderdetailUser">
		<!-- id配置結果集中主鍵的映射,result是非主鍵映射。 
		column是結果集字段,property定義將結果集字段映射到Order中哪一個屬性 -->
		<id column="id" property="id"/>
		<result column="user_id" property="user_id"/>
		<result column="number" property="number"/>
		<result column="createtime" property="createtime"/>
		<result column="note" property="note"/>
		
		<!-- 關聯User -->
		<!-- association將關聯信息映射到某個pojo。
		property定義將關聯信息映射到Order哪一個屬性,javaType定義這個屬性是什麼類型 -->
		<association property="user" javaType="User">
			<result column="sex" property="sex"/>
			<result column="username" property="username"/>
		</association>
		
		<!-- 關聯Orderdetial -->
		<!-- Order中關聯了Orderdetail集合,全部用collection -->
		<!-- property定義將關聯信息映射到Order哪一個屬性,ofType定義這個屬性是什麼類型 -->
		<collection property="orderDetails" ofType="OrderDetail">
			<id column="orderdetail_id" property="id"/>
			<result column="items_num" property="items_num"/>
			<result column="items_id" property="items_id"/>
		</collection>
	</resultMap>

	<!-- 一對多查詢, 查詢訂單信息關聯查詢訂單明細和用戶名、用戶性別,使用resultType -->
	<select id="findOrderOrderdetailUser" resultType="OrderQuery">
		SELECT
			orders.*, user.username,
			user.sex,
			orderdetail.id orderdetail_id,
			orderdetail.items_num,
			orderdetail.items_id
		FROM
			orders,
			orderdetail,
			user
		WHERE
			orders.user_id = user.id
			AND orders.id = orderdetail.orders_id;
	</select>
	
	<!-- 一對多查詢, 查詢訂單信息關聯查詢訂單明細和用戶名、用戶性別,使用resultMap -->
	<select id="findOrderOrderdetailUserByResultMap" resultMap="orderOrderdetailUser">
		SELECT
			orders.*, user.username,
			user.sex,
			orderdetail.id orderdetail_id,
			orderdetail.items_num,
			orderdetail.items_id
		FROM
			orders,
			orderdetail,
			user
		WHERE
			orders.user_id = user.id
			AND orders.id = orderdetail.orders_id;
	</select>

</mapper>

    接着一樣的根據sql映射文件寫mapper接口,寫測試類測試

    從上面咱們能夠看出,使用resultType開發複雜查詢須要建立新的pojo對象,而使用resultMap只須要修改原有的pojo對象。resultType要求結果集字段名和輸出映射屬性名一致,可是在複雜查詢中常常存在結果集字段衝突的狀況,這時候沒法使用resultType,而resultMap的映射特性正好可以解決這個問題。

五、延遲加載(Lazy Loading)

    在進行數據查詢時,爲了提升數據庫性能,儘可能使用單表查詢,由於單表查詢比多表關聯查詢速度要快。若是單表查詢就能知足需求,一開始先查詢單表,當須要關聯查詢時再關聯查詢,當須要關聯查詢再關聯查詢這樣的按需查詢特性叫作延遲加載。

    先定義一個需求:查詢訂單關聯查詢用戶id、username,一開始只查詢訂單信息,當須要查詢用戶信息時經過調用Order類的getUser()方法想數據庫發送sql。

     mybatis中經過resultMap實現延遲加載功能。在配置resultMap以前先要先配置全局配置文件,開啓延遲加載。開啓延遲加載須要在全局配置的settings元素中配置以下兩個參數:

    

<!-- mybatis行爲參數配置 -->
<settings>
    <!-- 開啓延遲加載 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 設置按需加載 -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

    接着在sql映射文件中配置resultMap

<resultMap type="Order" id="orderUser">
		<id column="id" property="id"/>
		<result column="user_id" property="user_id"/>
		<result column="number" property="number"/>
		<result column="createtime" property="createtime"/>
		<result column="note" property="note"/>
		
		<!-- 
			property: 將關聯查詢的信息設置到order哪一個屬性
			select: 延遲加載執行的sql的id,這裏是查詢User語句
			column:查詢User的sql語句中#{id}的參數值
		 -->
		<association property="user" column="user_id" select="ff.ing.mapper.UserMapper.findUserById"></association>
</resultMap>

    而後寫sql映射語句

<select id="findOrderUser" resultMap="orderUser">
		SELECT * FROM orders WHERE id = 3
</select>

    這個延遲加載demo由兩條語句組成,查詢訂單:SELECT * FROM orders WHERE id = 3 和 查詢用戶:SELECT * FROM user WHERE id = #{id}。當不須要查詢User時,只執行SELECT * FROM orders;當須要查詢User時,再執行SELECT * FROM user WHERE id = #{id},並把查詢Order得到到的結果集中的user_id看成參數傳入,得到這句sql的結果集最後由輸出映射自動封裝成新的結果集。

    接着在OrderMapper接口中添加findOrderUser方法,而後編寫測試方法。

public Order findOrderUser() throws Exception;
@Test
	public void testFindOrderUser() throws Exception {
		
		SqlSession sqlSession = sqlSessionFactory.openSession();
		
		OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
		Order order = orderMapper.findOrderUser();
		order.getUser();
		sqlSession.close();
	}

    最後測試,這部分測試須要日誌信息的支持,下一節再測試。

    注意:mybatis延遲加載特性依賴於cglib,而cglib依賴asm,因此要導入cglib和asm兩個jar包

六、使用log4j在控制檯輸出日誌,方便debug

    首先導入log4j的jar包和commons-logging

    

   而後在config目錄下配置log4j配置文件log4j.properties

# Global logging configuration\uff0c\u5efa\u8bae\u5f00\u53d1\u73af\u5883\u4e2d\u8981\u7528debug
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

   log4j.rootLogger指定日誌輸出級別爲debug,輸出方式是控制檯,   log4j.appender.stdout指定在控制檯輸出方式的實現類和其它一些參數

   log4j有多種日誌級別,分爲OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、ALL或者您定義的級別。Log4j建議只使用四個級別,優先級從高到低分別是ERROR、WARN、INFO、DEBUG。經過在這裏定義的級別,您能夠控制到應用程序中相應級別的日誌信息的開關。好比在這裏定義了INFO級別,則應用程序中全部DEBUG級別的日誌信息將不被打印出來。程序會打印高於或等於所設置級別的日誌,設置的日誌等級越高,打印出來的日誌就越少。若是設置級別爲INFO,則優先級高於等於INFO級別(如:INFO、WARN、ERROR)的日誌信息將能夠被輸出,小於該級別的如DEBUG將不會被輸出。

   如今咱們對延遲加載demo進行測試,在測試方法中order.getUser()這一行添加斷點,run as debug,而後咱們在控制檯會看到,當代碼沒有執行order.getUser()時只會執行SELECT * FROM orders WHERE id = 3語句,當執行order.getUser()時,才執行SELECT * FROM user WHERE id = #{id}語句。

六、緩存

    a、緩存的意義:將用戶常常查詢的數據放在緩存(內存)中,當用戶查詢這些數據時從緩存中查詢而不用從磁盤中(數據庫數據文件)查詢,從而提升查詢效率,解決高併發系統的性能問題。

    b、java的傳統三層架構中存在三類緩存:控制層緩存,業務層緩存和持久層緩存。以下圖

    

    c、mybatis實現持久層緩存,提供了一級緩存和二級緩存。

         一級緩存與二級緩存示意圖以下

        

        mybatis的一級緩存是一個SqlSession級別的緩存,SqlSession智能訪問本身的一級緩存數據,二級緩存是跨SqlSession的Mapper級別緩存,不一樣SqlSession能夠共享。

        一級緩存

           原理圖以下:

           

        mybatis默認支持一級緩存,不須要配置,每次查詢都先從緩存中查詢數據,沒有取到數據再從數據庫中查詢。第一次發出一個查詢sql,mybatis在緩存中找不到數據,因而到數據庫中查詢數據,並將sql返回結果集寫入一級緩存,數據結構爲Map<key, value>,key = hashcode + sql + sql輸入映射和輸出映射,value = 結果集。當同一個SqlSession再次發出相同sql時從緩存中取數據,不會到數據庫中查詢數據,當有commit操做時清空這個一級緩存中全部數據,此時再發送查詢sql又會到數據庫中查詢,由於緩存爲空。

       接下來對一級緩存特性進行測試。

public void testCacheLevel1() throws Exception{
		
		SqlSession sqlSession = sqlSessionFactory.openSession();
		
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
		User user1 = userMapper.findUserById(1);
		User user2 = userMapper.findUserById(1);
		System.out.println(user1);
		System.out.println(user2);
		sqlSession.close();
	}

        經過console輸出的日誌信息能夠看到sql語句只執行一次,第二次查詢時並無發送sql從數據庫中查詢。

            DEBUG [main] - Opening JDBC Connection

            DEBUG [main] - Created connection 20064246.

            DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@13227f6]

            DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE id = ? 

            DEBUG [main] - ==> Parameters: 1(Integer)

            DEBUG [main] - <==      Total: 1

            User [id=1, username=王五, sex=2, birthday=null, address=null]

            User [id=1, username=王五, sex=2, birthday=null, address=null]

    二級緩存

        原理圖以下:

        

        二級緩存是Mapper級別的緩存,以Mapper的命名空間爲單位建立緩存空間,供相同命名空間下的SqlSession訪問,不一樣命名空間下的SqlSession沒法共享同一個二級緩存空間,空間的數據結構也是Map<key, value>(二級緩存有多個緩存空間,一級緩存只有一個緩存空間)。每次查詢前先確認是否開啓二級緩存,若是開啓從二級緩存空間中查詢數據,若是未開啓會先從一級緩存中查詢數據,沒有查到匹配數據再從數據庫中查詢。

        二級緩存配置

            在全局配置文件SqlMapConfig.xml中加入

            <setting name="cacheEnabled" value="true"/>

     

            接着在Mapper映射文件中添加一行:<cache />,表示此Mapper開啓二級緩存。(位於mapper元素內,最前面)

            二級緩存須要將輸出映射的pojo實現序列化,若是映射中還有pojo也要實現序列化,不然會拋出異常。

            對二級緩存進行測試

@Test
	public void testCacheLevel2() throws Exception {

		SqlSession sqlSession1 = sqlSessionFactory.openSession();
		SqlSession sqlSession2 = sqlSessionFactory.openSession();

		UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
		UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
		User user1 = userMapper1.findUserById(1);
		sqlSession1.close();
		User user2 = userMapper2.findUserById(1);
		sqlSession2.close();
		System.out.println(user1);
		System.out.println(user2);
	}

            從console輸出的日誌信息中能夠看到,多了一行cache hit ration,說明正在從二級緩存中查詢數據,同時能夠看到sql只執行一次

                DEBUG [main] - Cache Hit Ratio [ff.ing.mapper.UserMapper]: 0.0

                DEBUG [main] - Opening JDBC Connection

                DEBUG [main] - Created connection 2282736.

                DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@22d4f0]

                DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE id = ? 

                DEBUG [main] - ==> Parameters: 1(Integer)

                DEBUG [main] - <==      Total: 1

                DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@22d4f0]

                DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@22d4f0]

                DEBUG [main] - Returned connection 2282736 to pool.

                DEBUG [main] - Cache Hit Ratio [ff.ing.mapper.UserMapper]: 0.5

                User [id=1, username=王五, sex=2, birthday=null, address=null]

                User [id=1, username=王五, sex=2, birthday=null, address=null]

        二級緩存的禁用與刷新

            對於變化頻率高的sql須要禁用二級緩存:在sql映射語句中添加屬性userCache='false',能夠禁用當前sql語句的二級緩存,userCache屬性默認值爲true。

            若是SqlSession執行commit操做會刷新二級緩存(清空),在sql映射語句中添加屬性flushCache能夠設置是否刷新緩存,默認值爲true。

        <cache>元素有許多配置,詳細配置參考在線手冊XML映射文件下cache一節

七、mybatis與spring整合

八、逆向工程

相關文章
相關標籤/搜索