在線參考文檔: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&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入門程序就寫完了。
附上項目目錄結構
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對象並返回。
a 數據庫鏈接建立和關閉頻繁,形成數據庫資源浪費,影響系統效率。改進:使用數據庫鏈接池。
b 代碼中sql語句硬編碼,若是需求變動須要修改sql,就必須修改java代碼,必須從新編譯,使系統不易維護。改進:將sql語句統一配置在文件中,修改sql語句時不須要修改java代碼。
c dao層開發中經過preparedStatement向佔位符設置參數存在硬編碼(參數位置,參數),使系統不易維護。改進:將sql中的佔位符和對應的參數類型配置在文件中,提供將參數自動注入到sql語句中的功能(輸入映射)。
d dao層開發中遍歷查詢結果集存在硬編碼(結果集列名硬編碼)。改進:實現sql查詢結果集向java對象的自動封裝功能。(輸出映射)
總結一下,用mybatis開發項目具備高靈活性,易於維護的優勢,mybatis還支持主流數據庫鏈接池,具備動態sql的特性,能夠輕鬆的根據不一樣條件拼接sql,因此樂意用它。
mybatis適用於開發需求變動頻繁的系統,好比互聯網項目;傳統jdbc開發方式或框架,如hibernate適用於需求固定,對象數據模型穩定的項目,好比企業OA系統。
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數列
首先定義一個需求:根據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提供一套簡化開發的規範(包括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&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>
到這裏,簡化開發大功告成。
定一個需求:查詢訂單關聯查詢訂單明細和用戶名、用戶性別
數據模型說明: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的映射特性正好可以解決這個問題。
在進行數據查詢時,爲了提升數據庫性能,儘可能使用單表查詢,由於單表查詢比多表關聯查詢速度要快。若是單表查詢就能知足需求,一開始先查詢單表,當須要關聯查詢時再關聯查詢,當須要關聯查詢再關聯查詢這樣的按需查詢特性叫作延遲加載。
先定義一個需求:查詢訂單關聯查詢用戶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的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一節