什麼是 MyBatis?
MyBatis 是一款優秀的持久層框架,它支援自定義 SQL、儲存過程以及高階對映。MyBatis 除了幾乎所有的 JDBC 程式碼以及設定引數和獲取結果集的工作。MyBatis 可以透過簡單的 XML 或註解來配置和對映原始型別、介面和 Java POJO(Plain Old Java Objects,普通老式 Java 物件)為資料庫中的記錄。
使用步驟1. Maven依賴<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>x.x.x</version></dependency>
使用mysql的driver。
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.22</version></dependency>
2. XML中構建 SqlSessionFactory
XML 配置檔案中包含了對 MyBatis 系統的核心設定,包括獲取資料庫連線例項的資料來源(DataSource)以及決定事務作用域和控制方式的事務管理器(TransactionManager)。
<?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"><configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers></configuration>
3. 編寫工具類這就可以根據config.xml檔案中資料庫的連線配置來建立SqlSessionFactory,並編寫提供sqlSession的方法。
public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); }}
4. 編寫實體類
根據資料庫中的資料編寫。
public class User { private int id; private String name; private String pwd; public User() { } public User(int id, String name, String pwd) { this.id = id; this.name = name; this.pwd = pwd; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", pwd='" + pwd + '\'' + '}'; }}
5. 編寫DAO層public interface UserDao { List<User> getUserList();}
原本的JDBC中需要編寫具體的實現類,從而執行SQL語句。但是Mybatis透過使用XML定義的方式取代了編寫實現類,並取名為Mapper,其中namespace實現介面繫結,select中的id屬性就是介面中的方法,resultType表示了返回值的型別,就是我們編寫的實體類。
<?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.zzy.dao.UserDao"> <select id="getUserList" resultType="com.zzy.pojo.User"> select * from mybatis.test </select></mapper>
此時還需要在Mybatis的config.xml中配置,指定mapper的路徑。
6. 編寫測試類透過工具類MybatisUtils獲取SqlSession,getmapper執行mapper中的配置,相當於構建介面的實現類,最後呼叫了getUserList,執行getUserList方法,返回查詢的結果,最後一定記得將資源關閉。
@Testpublic void test() { SqlSession sqlSession = MybatisUtils.getSqlSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); List<User> userList = userDao.getUserList(); for(User user : userList){ System.out.println(user); } sqlSession.close();}
生命週期和作用域SqlSessionFactoryBuilder
這個類可以被例項化、使用和丟棄,一旦建立了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 例項的最佳作用域是方法作用域(也就是區域性方法變數)。 你可以重用 SqlSessionFactoryBuilder 來建立多個 SqlSessionFactory 例項,但最好還是不要一直保留著它,以保證所有的 XML 解析資源可以被釋放給更重要的事情。
SqlSessionFactorySqlSessionFactory 一旦被建立就應該在應用的執行期間一直存在,沒有任何理由丟棄它或重新建立另一個例項。 使用 SqlSessionFactory 的最佳實踐是在應用執行期間不要重複建立多次,多次重建 SqlSessionFactory 被視為一種程式碼“壞習慣”。因此 SqlSessionFactory 的最佳作用域是應用作用域。 有很多方法可以做到,最簡單的就是使用單例模式或者靜態單例模式。
SqlSession每個執行緒都應該有它自己的 SqlSession 例項。SqlSession 的例項不是執行緒安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域。 絕對不能將 SqlSession 例項的引用放在一個類的靜態域,甚至一個類的例項變數也不行。 也絕不能將 SqlSession 例項的引用放在任何型別的託管作用域中,比如 Servlet 框架中的 HttpSession。 如果你現在正在使用一種 Web 框架,考慮將 SqlSession 放在一個和 HTTP 請求相似的作用域中。 換句話說,每次收到 HTTP 請求,就可以開啟一個 SqlSession,返回一個響應後,就關閉它。 這個關閉操作很重要,為了確保每次都能執行關閉操作,你應該把這個關閉操作放到 finally 塊中。 下面的示例就是一個確保 SqlSession 關閉的標準模式:
try (SqlSession session = sqlSessionFactory.openSession()) { // 你的應用邏輯程式碼}
如果不及時關閉,會造成大量的資源浪費。
對映器例項對映器是一些繫結對映語句的介面。對映器介面的例項是從 SqlSession 中獲得的。雖然從技術層面上來講,任何對映器例項的最大作用域與請求它們的 SqlSession 相同。但方法作用域才是對映器例項的最合適的作用域。 也就是說,對映器例項應該在呼叫它們的方法中被獲取,使用完畢之後即可丟棄。 對映器例項並不需要被顯式地關閉。儘管在整個請求作用域保留對映器例項不會有什麼問題,但是你很快會發現,在這個作用域上管理太多像 SqlSession 的資源會讓你忙不過來。 因此,最好將對映器放在方法作用域內。就像下面的例子一樣:
try (SqlSession session = sqlSessionFactory.openSession()) { BlogMapper mapper = session.getMapper(BlogMapper.class); // 你的應用邏輯程式碼}
XML配置enviroments
mybatis可以配置多種環境,這種機制有助於SQL對映到多種資料庫中, 現實情況下有多種理由需要這麼做。例如,開發、測試和生產環境需要有不同的配置;或者現在具有相同 Schema 的多個生產資料庫中使用相同的 SQL 對映。還有許多類似的使用場景。
但是每個sqlsessionfactory只能對應一個環境,而每個資料庫對應一個sqlsessionfactory。
為了指定建立哪種環境,只要將它作為可選的引數傳遞給 SqlSessionFactoryBuilder 即可。可以接受環境配置的兩個方法簽名是:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
如果沒有寫明指定的環境,就會使用預設環境,default=環境id,就是預設的環境。
事務管理器(transactionManager)
在 MyBatis 中有兩種型別的事務管理器(也就是 type="[JDBC|MANAGED]")
● JDBC – 這個配置直接使用了 JDBC 的提交和回滾設施,它依賴從資料來源獲得的連線來管理事務作用域。● MANAGED – 這個配置幾乎沒做什麼。它從不提交或回滾一個連線,而是讓容器來管理事務的整個生命週期(比如 JEE 應用伺服器的上下文)。 預設情況下它會關閉連線。然而一些容器並不希望連線被關閉,因此需要將 closeConnection 屬性設定為 false 來阻止預設的關閉行為。例如:
<transactionManager type="MANAGED"> <property name="closeConnection" value="false"/></transactionManager>
資料來源(dataSource)
dataSource 元素使用標準的 JDBC 資料來源介面來配置 JDBC 連線物件的資源。
有三種內建的資料來源型別(也就是 type="[UNPOOLED|POOLED|JNDI]"):
UNPOOLED–這個資料來源的實現會每次請求時開啟和關閉連線。雖然有點慢,但對那些資料庫連線可用性要求不高的簡單應用程式來說,是一個很好的選擇。POOLED– 這種資料來源的實現利用“池”的概念將 JDBC 連線物件組織起來,避免了建立新的連線例項時所必需的初始化和認證時間。這種處理方式很流行,能使併發 Web 應用快速響應請求。JNDI – 這個資料來源實現是為了能在如 EJB或應用伺服器這類容器中使用,容器可以集中或在外部配置資料來源,然後放置一個 JNDI 上下文的資料來源引用。屬性(properties)
property可以透過編寫.properties檔案來配置,或者直接在environment中宣告,如果使用.properties就需要在properties的resource中指明路徑,然後在環境中使用properties時,使用如下形式,${}裡面對應的就是.properties檔案中屬性名。
如果同時.properties和在xml檔案配置,那麼就會優先使用.properties。
類型別名(typeAliases)類型別名可為 Java 型別設定一個縮寫名字。 它僅用於 XML 配置,意在降低冗餘的全限定類名書寫。兩種宣告方式:
給每一個類起一個具體的別名,這種方式的好處是可以自己指定。
掃描包下的類,自動給類起別名,別名預設為這個類的首字母小寫的名字。比如com.blog.User的別名為user,可以在類上新增Alias註解也可以實現自定義別名。
@Alias("author")public class Author { ...}
設定(settings)
這是 MyBatis 中極為重要的調整設定,它們會改變 MyBatis 的執行時行為。 下表描述了設定中各項設定的含義、預設值等。
主要掌握:
log4j
Log4j是Apache的一個開源專案,透過使用Log4j,我們可以控制日誌資訊輸送的目的地是控制檯、檔案、GUI元件,甚至是套介面伺服器、NT的事件記錄器、UNIX Syslog守護程序等;我們也可以控制每一條日誌的輸出格式;透過定義每一條日誌資訊的級別,我們能夠更加細緻地控制日誌的生成過程。最令人感興趣的就是,這些可以透過一個配置檔案來靈活地進行配置,而不需要修改應用的程式碼。
使用步驟:第一步:在maven中匯入jar包
log4j.rootLogger=DEBUG,console,filelog4j.appender.console = org.apache.log4j.ConsoleAppenderlog4j.appender.console.Target = System.outlog4j.appender.console.Threshold=DEBUGlog4j.appender.console.layout = org.apache.log4j.PatternLayoutlog4j.appender.console.layout.ConversionPattern = [%c]-%m%nlog4j.appender.file = org.apache.log4j.RollingFileAppenderlog4j.appender.file.File = ./log/zzy.loglog4j.appender.file.MaxFileSize = 10mblog4j.appender.file.Threshold=DEBUGlog4j.appender.file.layout=org.apache.log4j.PatternLayoutlog4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%nlog4j.appender.org.mybatis=DEBUGlog4j.appender.java.sql=DEBUGlog4j.appender.java.sql.Statement=DEBUGlog4j.appender.java.sql.ResultSet=DEBUGlog4j.appender.java.sql.PreparedStatement=DEBUG
第三步:配置
注意要完全一樣,大小寫、空格等不同都會導致錯誤。
第四步:獲取當前使用類的物件。
static Logger logger = Logger.getLogger(xxx.class);
效果:
對映器(mappers)
既然 MyBatis 的行為已經由上述元素配置完了,我們現在就要來定義 SQL 對映語句了。 但首先,我們需要告訴 MyBatis 到哪裡去找到這些語句。
XML對映檔案引數對映鑑於引數型別(parameterType)會被自動設定為int,這個引數可以隨意命名。
如果 User 型別的引數物件傳遞到了語句中,會查詢 id、username 和 password 屬性,然後將它們的值傳入預處理語句的引數中。
<insert id="insertUser" parameterType="User"> insert into users (id, username, password) values (#{id}, #{username}, #{password})</insert>
提示 :JDBC 要求,如果一個列允許使用 null 值,並且會使用值為 null 的引數,就必須要指定 JDBC 型別(jdbcType)。
儘管上面這些選項很強大,但大多時候,你只須簡單指定屬性名,頂多要為可能為空的列指定jdbcType,其他的事情交給 MyBatis 自己去推斷就行了。
#{firstName}#{middleInitial,jdbcType=VARCHAR}#{lastName}
使用@Param 可以指定mapper接收的引數名。
@Select("select * from user where ${column} = #{value}")User findByColumn(@Param("column") String column,@Param("value") String value);
結果對映
MyBatis 的真正強大在於它的語句對映,這是它的魔力所在。由於它的異常強大,對映器的 XML 檔案就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 程式碼進行對比,你會立即發現省掉了將近 95% 的程式碼。MyBatis 致力於減少使用成本,讓使用者能更專注於SQL程式碼。
resultMap
resultmap元素是 MyBatis 中最重要最強大的元素。它可以讓你從 90% 的 JDBC resultset資料提取程式碼中解放出來,並在一些情形下允許你進行一些 JDBC 不支援的操作。實際上,在為一些比如連線的複雜語句編寫對映程式碼的時候,一份 resultmap 能夠代替實現同等功能的數千行程式碼。ResultMap 的設計思想是,對簡單的語句做到零配置,對於複雜一點的語句,只需要描述語句之間的關係就行了。
<select id="selectUsers" resultMap="userResultMap"> select user_id, user_name, hashed_password from some_table where id = #{id}</select><resultMap id="userResultMap" type="User"> <id property="id" column="user_id" /> <result property="username" column="user_name"/> <result property="password" column="hashed_password"/></resultMap>
ResultMap的優秀之處——你完全可以不用顯式地配置它們,當實體類的屬性名和資料庫的列名一樣時,就完全不需要使用(自動對映),只需要在不相同的時候配置即可。
動態SQLif使用動態 SQL 最常見情景是根據條件包含 where 子句的一部分。比如:
<select id="findActiveBlogWithTitleLike" resultType="Blog"> SELECT * FROM BLOG WHERE state = ‘ACTIVE’ <if test="title != null"> AND title like #{title} </if></select>
如果希望透過 “title” 和 “author” 兩個引數進行可選搜尋該怎麼辦呢?首先,我想先將語句名稱修改成更名副其實的名稱;接下來,只需要加入另一個條件即可。
<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG WHERE state = ‘ACTIVE’ <if test="title != null"> AND title like #{title} </if> <if test="author != null and author.name != null"> AND author_name like #{author.name} </if></select>
choose、when、otherwise
有時候,我們不想使用所有的條件,而只是想從多個條件中選擇一個使用。針對這種情況,MyBatis 提供了 choose 元素,它有點像 Java 中的 switch 語句。
<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG WHERE state = ‘ACTIVE’ <choose> <when test="title != null"> AND title like #{title} </when> <when test="author != null and author.name != null"> AND author_name like #{author.name} </when> <otherwise> AND featured = 1 </otherwise> </choose></select>
trim、where、set現在看下面這個例子,當沒有一個匹配時,sql就為:select * from blog where;這種顯然是錯誤的語句,那麼如果第二個語句成立時,語句變為:select * from blog where and title like #{title};顯然也是錯誤的語句。
<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG WHERE <if test="state != null"> state = #{state} </if> <if test="title != null"> AND title like #{title} </if> <if test="author != null and author.name != null"> AND author_name like #{author.name} </if></select>
MyBatis 有一個簡單且適合大多數場景的解決辦法。而在其他場景中,可以對其進行自定義以符合需求。而這,只需要一處簡單的改動:
<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG <where> <if test="state != null"> state = #{state} </if> <if test="title != null"> AND title like #{title} </if> <if test="author != null and author.name != null"> AND author_name like #{author.name} </if> </where></select>
where 元素只會在子元素返回任何內容的情況下才插入 “WHERE” 子句。而且,若子句的開頭為 “AND” 或 “OR”,where 元素也會將它們去除。
如果 where 元素與你期望的不太一樣,你也可以透過自定義 trim 元素來定製 where 元素的功能。比如,和 where 元素等價的自定義 trim 元素為:
<trim prefix="WHERE" prefixOverrides="AND |OR "> ...</trim>
prefixOverrides 屬性會忽略透過管道符分隔的文字序列(注意此例中的空格是必要的)。上述例子會移除所有 prefixOverrides 屬性中指定的內容,並且插入 prefix 屬性中指定的內容。
用於動態更新語句的類似解決方案叫做 set。set 元素可以用於動態包含需要更新的列,忽略其它不更新的列。比如:
<update id="updateAuthorIfNecessary"> update Author <set> <if test="username != null">username=#{username}, </if> <if test="password != null">password=#{password}, </if> <if test="email != null">email=#{email}, </if> <if test="bio != null">bio=#{bio} </if> </set> where id=#{id}</update>
這個例子中,set 元素會動態地在行首插入 SET 關鍵字,並會刪掉額外的逗號(這些逗號是在使用條件語句給列賦值時引入的)。
來看看與 set 元素等價的自定義 trim 元素吧:
<trim prefix="SET" suffixOverrides=","> ...</trim>
foreach
動態 SQL 的另一個常見使用場景是對集合進行遍歷(尤其是在構建 IN 條件語句的時候)。比如:
<select id="selectPostIn" resultType="domain.blog.Post"> SELECT * FROM POST P WHERE ID in <foreach item="item" index="index" collection="list" open="(" separator="," close=")"> #{item} </foreach></select>
foreach 元素的功能非常強大,它允許你指定一個集合,宣告可以在元素體內使用的集合項(item)和索引(index)變數。它也允許你指定開頭與結尾的字串以及集合項迭代之間的分隔符。這個元素也不會錯誤地新增多餘的分隔符,看它多智慧!
提示:你可以將任何可迭代物件(如 List、Set 等)、Map 物件或者陣列物件作為集合引數傳遞給 foreach。當使用可迭代物件或者陣列時,index 是當前迭代的序號,item 的值是本次迭代獲取到的元素。當使用 Map 物件(或者 Map.Entry 物件的集合)時,index 是鍵,item 是值。
bind<select id="selectBlogsLike" resultType="Blog"> <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" /> SELECT * FROM BLOG WHERE title LIKE #{pattern}</select>
快取基本概念
MyBatis 內建了一個強大的事務性查詢快取機制,它可以非常方便地配置和定製。 為了使它更加強大而且易於配置,我們對 MyBatis 3 中的快取實現進行了許多改進。
預設情況下,只啟用了本地的會話快取,它僅僅對一個會話中的資料進行快取。 要啟用全域性的二級快取,只需要在你的 SQL 對映檔案中新增一行:
<cache/>
同時還需要在settings中宣告cacheEnable,雖然是預設為true的。
基本上就是這樣。這個簡單語句的效果如下:
對映語句檔案中的所有 select 語句的結果將會被快取。對映語句檔案中的所有 insert、update 和 delete語句會重新整理快取。快取會使用最近最少使用演算法(LRU, Least Recently Used)演算法來清除不需要的快取。快取不會定時進行重新整理(也就是說,沒有重新整理間隔)。快取會儲存列表或物件(無論查詢方法返回哪種)的 1024 個引用。快取會被視為讀/寫快取,這意味著獲取到的物件並不是共享的,可以安全地被呼叫者修改,而不干擾其他呼叫者或執行緒所做的潛在修改。提示:快取只作用於 cache 標籤所在的對映檔案中的語句。如果你混合使用 Java API 和 XML 對映檔案,在共用介面中的語句將不會被預設快取。你需要使用 @CacheNamespaceRef 註解指定快取作用域。
這些屬性可以透過 cache 元素的屬性來修改。
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
這個更高階的配置建立了一個 FIFO 快取,每隔 60 秒重新整理,最多可以儲存結果物件或列表的 512 個引用,而且返回的物件被認為是隻讀的,因此對它們進行修改可能會在不同執行緒中的呼叫者產生衝突。
可用的清除策略有:
預設的清除策略是 LRU。
flushInterval(重新整理間隔)屬性可以被設定為任意的正整數,設定的值應該是一個以毫秒為單位的合理時間量。 預設情況是不設定,也就是沒有重新整理間隔,快取僅僅會在呼叫語句時重新整理。
size(引用數目)屬性可以被設定為任意正整數,要注意欲快取物件的大小和執行環境中可用的記憶體資源。預設值是 1024。
readOnly(只讀)屬性可以被設定為 true 或 false。只讀的快取會給所有呼叫者返回快取物件的相同例項。 因此這些物件不能被修改。這就提供了可觀的效能提升。而可讀寫的快取會(透過序列化)返回快取物件的複製。 速度上會慢一些,但是更安全,因此預設值是 false。
工作流程二級快取髒讀由於二級快取作用於namesace級別,也就是說不同mapper有自己獨立一套的快取,那麼當不同mapper對同一表進行操作時,就會出現髒讀。
比如mapper A 查詢了 M 表 ,然後mapper B 對 M表修改並提交事務,只要mapper A這段時間內沒有執行update、delete、insert操作,mapper A的快取就不會被重新整理,當mapper A再次select時,由於Mybatis快取工作機制,會先從二級快取中拿取資料,此時mapper A select出的資料就是髒資料。
因此在開啟二級快取後,所有的對同一表的增刪改查應該在同一mapper下。
Mybatis執行流程resource讀取配置檔案解析配置檔案流XMLConfiguration例項化SqlSessionFactorytransactionManagerexecutor建立SqlSession實現CRUD結語感謝你看到這裡,覺得文章對你有幫助的話記得轉發一下文章,記得轉發+轉發+轉發!