MyBatis(一)MyBatis介绍和配置详解
?
在Java程序里面去操作數(shù)據(jù)庫(kù),最原始的辦法是使用JDBC的API。需要分為六步:
- 注冊(cè)驅(qū)動(dòng)
- 通過(guò)DriverManager獲取一個(gè)Connection
- 通過(guò)Connection創(chuàng)建一個(gè)Statement對(duì)象。
- 通過(guò)Statement的execute()方法執(zhí)行SQL,返回結(jié)果集ResultSet
- 將ResultSet結(jié)果集轉(zhuǎn)換成POJO對(duì)象。
- 關(guān)閉各種資源
像Connection獲取,結(jié)果集的封裝,資源關(guān)閉這些代碼在開(kāi)發(fā)的過(guò)程中會(huì)出現(xiàn)大量冗余,所以出現(xiàn)了很多對(duì)JDBC進(jìn)行封裝的框架來(lái)簡(jiǎn)化操作。
Apache DbUtils:
DbUtils 解決的最核心的問(wèn)題就是結(jié)果集的映射,提供了一系列的支持泛型的ResultSetHandler,幫助我們把 ResultSet 封裝成JavaBean。
DbUtils提供了一個(gè)QueryRunner類,它對(duì)數(shù)據(jù)庫(kù)的增刪改查的方法進(jìn)行了封裝, 在 QueryRunner 的構(gòu)造函數(shù)里面,我們可以傳入一個(gè)數(shù)據(jù)源DataSource,讓框架幫我們管理連接。
注意:DbUtils要求數(shù)據(jù)庫(kù)的字段跟對(duì)象的屬性名稱完全一致,才可以實(shí)現(xiàn)自動(dòng)映射。
Spring JDBC
Spring也對(duì)原生的JDBC進(jìn)行了封裝,并且給我們提供了一個(gè)模板方法JdbcTemplate,來(lái)簡(jiǎn)化我們對(duì)數(shù)據(jù)庫(kù)的操作。
和dbutils類似,spring幫助我們管理datasource和connection,并且提供了RowMapper接口,我們只要實(shí)現(xiàn)RowMapper接口,并且重寫mapRow()方法,就可以將結(jié)果集轉(zhuǎn)換成Java對(duì)象。
上面兩種都是對(duì)JDBC筆記簡(jiǎn)單的封裝,雖然簡(jiǎn)化了操作,但是缺少一些擴(kuò)展功能,比方說(shuō)SQL語(yǔ)句還是需要在代碼里硬編碼,不便于修改,無(wú)法生成動(dòng)態(tài)SQL,沒(méi)有緩存等
這就有了功能更加豐富的ORM框架(Object Relational Mapping),幫助我們解決程序?qū)ο蠛完P(guān)系型數(shù)據(jù)庫(kù)的相互映射的問(wèn)題。
MyBatis使用
MyBatis 就是一個(gè)“半自動(dòng)化”的ORM框架(一般稱Hibernate為全自動(dòng)化),它的封裝程度沒(méi)有Hibernate那么高,不會(huì)自動(dòng)生成全部的SQL語(yǔ)句,但是相對(duì)性能也更好一些 ?(框架封裝的越多,說(shuō)明內(nèi)部處理越多,相對(duì)來(lái)說(shuō)性能越差,原生的JDBC API的性能反而是最好的)
先看看在單獨(dú)使用Mybatis的時(shí)候我們是怎么配置的
- 引入Mybatis jar包
- 創(chuàng)建一個(gè)全局配置文件mybatis-config.xml?
- 創(chuàng)建映射器文件,Mapper.xml,通常來(lái)說(shuō)一張表對(duì)應(yīng)一個(gè)Mapper文件
使用demo
public void Test() throws IOException {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession session = sqlSessionFactory.openSession();try {//執(zhí)行SQL有兩種方法://1.通過(guò)SqlSession接口上的方法,傳入Statement ID來(lái)執(zhí)行SQL//2.定義一個(gè)Mapper接口。這個(gè)接口全路徑必須跟Mapper.xml里面的namespace對(duì)應(yīng)起來(lái),方法也要跟Statement ID一一對(duì)應(yīng)BlogMapper mapper = session.getMapper(BlogMapper.class);Blog blog = mapper.selectBlogById(1);System.out.println(blog);} finally {session.close();}}從上面的代碼可以看出MyBatis的幾大核心對(duì)象:SqlSessionFactoryBuiler、SqlSessionFactory、SqlSession 和Mapper對(duì)象
核心對(duì)象
1)SqlSessionFactoryBuilder
用來(lái)構(gòu)建 SqlSessionFactory 的(建造者模式),SqlSessionFactory作為會(huì)話工廠只需要一個(gè)就夠了,所以只要?jiǎng)?chuàng)建了SqlSessionFactory,Builder就可以銷毀了 所以它的生命周期只存在于方法的局部。
2)SqlSessionFactory
SqlSessionFactory 是用來(lái)創(chuàng)建SqlSession的,每次應(yīng)用程序訪問(wèn)數(shù)據(jù)庫(kù),都需要?jiǎng)?chuàng)建一個(gè)會(huì)話。所以 SqlSessionFactory 應(yīng)該存在于應(yīng)用的整個(gè)生命周期中(作用域是應(yīng)用作用域),并且是單例的。
3)SqlSession
SqlSession是一個(gè)會(huì)話,因?yàn)樗皇蔷€程安全的,所以不能在線程間共享。每次請(qǐng)求開(kāi)始的時(shí)候都需要?jiǎng)?chuàng)建一個(gè)SqlSession對(duì)象,在請(qǐng)求結(jié)束的時(shí)候要及時(shí)關(guān)閉它 ? ?作用域:一次請(qǐng)求或者交互中
4)Mapper
Mapper(實(shí)際上是一個(gè)代理對(duì)象 后面會(huì)分析)的作用是發(fā)送SQL來(lái)操作數(shù)據(jù)庫(kù),是從SqlSession中獲取的,所以它的作用域應(yīng)該和SqlSession相關(guān),在一個(gè) SqlSession事務(wù)方法之內(nèi)才有效
| 對(duì)象 | 生命周期 |
| SqlSessionFactoryBuiler | 方法局部(method) |
| SqlSessionFactory(單例) | 應(yīng)用級(jí)別(application) |
| SqlSession | 請(qǐng)求和操作(request/method) |
| Mapper | 方法(method)? |
?
?
?
?
?
?
核心配置解讀
<?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><properties resource="db.properties"></properties><settings><!-- 打印查詢語(yǔ)句 --><setting name="logImpl" value="STDOUT_LOGGING" /><!-- 控制全局緩存(二級(jí)緩存)--><setting name="cacheEnabled" value="true"/><!-- 延遲加載的全局開(kāi)關(guān)。當(dāng)開(kāi)啟時(shí),所有關(guān)聯(lián)對(duì)象都會(huì)延遲加載。默認(rèn) false --><setting name="lazyLoadingEnabled" value="true"/><!-- 當(dāng)開(kāi)啟時(shí),任何方法的調(diào)用都會(huì)加載該對(duì)象的所有屬性。默認(rèn) false,可通過(guò)select標(biāo)簽的 fetchType來(lái)覆蓋--><setting name="aggressiveLazyLoading" value="false"/><!-- Mybatis 創(chuàng)建具有延遲加載能力的對(duì)象所用到的代理工具,默認(rèn)JAVASSIST --><!--<setting name="proxyFactory" value="CGLIB" />--><setting name="localCacheScope" value="SESSION"/></settings><typeAliases><typeAlias alias="blog" type="com.chenpp.domain.Blog" /></typeAliases><typeHandlers><typeHandler handler="com.chenpp.type.MyTypeHandler"></typeHandler></typeHandlers><!-- 對(duì)象工廠 --> <!-- <objectFactory type="com.chenpp.objectfactory.CPObjectFactory"><property name="name" value="cpp"/></objectFactory>--><!-- <plugins><plugin interceptor="com.chenpp.interceptor.MyPageInterceptor"></plugin></plugins>--><environments default="development"><environment id="development"><transactionManager type="JDBC"/><!-- 單獨(dú)使用時(shí)配置成MANAGED沒(méi)有事務(wù) --><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><mappers><mapper resource="BlogMapper.xml"/></mappers></configuration>一級(jí)標(biāo)簽
configuration
configuration 是整個(gè)配置文件的根標(biāo)簽,對(duì)應(yīng)?MyBatis 里面最重要的配置類 Configuration(單例)
properties
用來(lái)配置參數(shù)信息,比如最常見(jiàn)的數(shù)據(jù)庫(kù)連接信息。
為了避免直接把參數(shù)寫死在 xml 配置文件中,我們可以把這些參數(shù)單獨(dú)放在properties 文件中,用 properties 標(biāo)簽引入進(jìn)來(lái),然后在xml 配置文件中用${}引用就可以了。
settings
setttings里面是MyBatis的一些核心配置,各種參數(shù)的配置
https://mybatis.org/mybatis-3/zh/configuration.html#settings
| 屬性名? | 描述 | 有效值 | 默認(rèn)值 |
| cacheEnabled | 全局地開(kāi)啟或關(guān)閉配置文件中的所有映射器已經(jīng)配置的任何緩存。 | true | false | true |
| lazyLoadingEnabled | 延遲加載的全局開(kāi)關(guān)。當(dāng)開(kāi)啟時(shí),所有關(guān)聯(lián)對(duì)象都會(huì)延遲加載。 特定關(guān)聯(lián)關(guān)系中可通過(guò)設(shè)置?fetchType?屬性來(lái)覆蓋該項(xiàng)的開(kāi)關(guān)狀態(tài)。 | true | false | false |
| aggressiveLazyLoading | 當(dāng)開(kāi)啟時(shí),任何方法的調(diào)用都會(huì)加載該對(duì)象的所有屬性。 否則,每個(gè)屬性會(huì)按需加載(參考?lazyLoadTriggerMethods)。 | true | false | false? |
| defaultExecutorType | 配置默認(rèn)的執(zhí)行器。SIMPLE 就是普通的執(zhí)行器;REUSE 執(zhí)行器會(huì)重用預(yù)處理語(yǔ)句(prepared statements); BATCH 執(zhí)行器將重用語(yǔ)句并執(zhí)行批量更新。 | SIMPLE REUSE BATCH | SIMPLE |
| lazyLoadTriggerMeth ods ? | 指定哪個(gè)對(duì)象的方法觸發(fā)一次延遲加載。 | 用逗號(hào)分隔的方法列表。 | equals,clone hashCode,toString |
| localCacheScope | MyBatis 利用本地緩存機(jī)制(Local Cache)防止循環(huán)引用(circular references)和加速重復(fù)嵌套查詢。 默認(rèn)值為 SESSION,這種情況下會(huì)緩存一個(gè)會(huì)話中執(zhí)行的所有查詢。 若設(shè)置值為 STATEMENT,本地會(huì)話僅用在語(yǔ)句執(zhí)行上,對(duì)相同 SqlSession 的不同調(diào)用將不會(huì)共享數(shù)據(jù) | SESSION | STATEMENT | SESSION |
| logImpl | 指定 MyBatis 所用日志的具體實(shí)現(xiàn),未指定時(shí)將自動(dòng)查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未設(shè)置 |
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
typeHandlers
用于把Java對(duì)象轉(zhuǎn)換為數(shù)據(jù)庫(kù)的值,或者把數(shù)據(jù)庫(kù)的值轉(zhuǎn)換成 Java 對(duì)象;比放說(shuō)(String和varchar)
對(duì)于一些基礎(chǔ)類型,MyBatis里內(nèi)置了一些基本的TypeHandler, 都注冊(cè)在TypeHandlerRegistry,他們都繼承了抽象類BaseTypeHandler,我們也可以實(shí)現(xiàn)自己的TypeHandler
需要實(shí)現(xiàn)以下四個(gè)抽象方法
使用的時(shí)候,需要先在mybatis-config.xml里注冊(cè)對(duì)應(yīng)的TypeHandler
<!-- mybatis-config.xml --> <typeHandlers><typeHandler handler="org.mybatis.example.ExampleTypeHandler"/> </typeHandlers>然后在我們需要使用的字段上指定對(duì)應(yīng)的typeHandler就可以了
#插入 <insert id="insertBlog" parameterType="blog">insert into blog (bid, name, author_id)values (#{bid,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR,typeHandler=com.chenpp.type.MyTypeHandler}, #{authorId,jdbcType=CHAR}) </insert> #查詢結(jié)果映射<resultMap id="BaseResultMap" type="blog"><id column="bid" property="bid" jdbcType="INTEGER"/><result column="name" property="name" jdbcType="VARCHAR" typeHandler="com.chenpp.type.MyTypeHandler"/><result column="author_id" property="authorId" jdbcType="INTEGER"/> </resultMap>objectFactory
ObjectFactory用于創(chuàng)建實(shí)體類的實(shí)例,當(dāng)我們把數(shù)據(jù)庫(kù)返回的結(jié)果集轉(zhuǎn)換為實(shí)體類的時(shí)候,就會(huì)使用ObjectFactory通過(guò)反射來(lái)創(chuàng)建對(duì)象,里面定義了4個(gè)方法
ObjectFactory 有一個(gè)默認(rèn)的實(shí)現(xiàn)類DefaultObjectFactory,創(chuàng)建對(duì)象的方法最終都調(diào)用了instantiateClass(),是通過(guò)反射來(lái)完成實(shí)例化的
如果想要修改對(duì)象工廠在映射實(shí)體類時(shí)的行為,就可以創(chuàng)建自己的對(duì)象工廠,只需要繼承DefaultObjectFactory然后重寫create()方法就可以了
plugins
插件是MyBatis的一個(gè)很強(qiáng)大的機(jī)制,跟很多其他的框架一樣,MyBatis預(yù)留了插件的接口,讓MyBatis更容易擴(kuò)展。這個(gè)會(huì)在后面詳細(xì)分析
environments、environment
environments標(biāo)簽用來(lái)管理數(shù)據(jù)庫(kù)的環(huán)境,比如我們可以有開(kāi)發(fā)環(huán)境、測(cè)試環(huán)境、生產(chǎn)環(huán)境等不同環(huán)境的數(shù)據(jù)庫(kù)。可以在不同的環(huán)境中使用不同的數(shù)據(jù)庫(kù)。這里面有兩個(gè)關(guān)鍵的標(biāo)簽,一個(gè)是事務(wù)管理器,一個(gè)是數(shù)據(jù)源
transactionManager
如果配置的是JDBC,就會(huì)直接使用?JDBC 的提交和回滾設(shè)置,它依賴于從數(shù)據(jù)源得到的連接來(lái)管理事務(wù)作用域
如果配置成MANAGED,會(huì)把事務(wù)交給容器來(lái)管理,比如JBOSS,Weblogic等。如果直接在本地環(huán)境運(yùn)行程序,配置成MANAGE的話不會(huì)有任何事務(wù)。
dataSource
dataSource 元素使用標(biāo)準(zhǔn)的 JDBC 數(shù)據(jù)源接口來(lái)配置 JDBC 連接對(duì)象的資源。有三種內(nèi)建的數(shù)據(jù)源類型(也就是UNPOOLED,POOLED,JNDI)
UNPOOLED– 這個(gè)數(shù)據(jù)源的實(shí)現(xiàn)只是每次請(qǐng)求時(shí)打開(kāi)和關(guān)閉連接。
POOLED–使用來(lái)連接池管理?JDBC 連接對(duì)象,復(fù)用連接
JNDI?– 這個(gè)數(shù)據(jù)源的實(shí)現(xiàn)是為了能在如 EJB 或應(yīng)用服務(wù)器這類容器中使用
在跟Spring集成的時(shí)候,事務(wù)和數(shù)據(jù)源都會(huì)交給Spring來(lái)管理。
mappers
<mappers>標(biāo)簽配置的是我們的映射器,也就是Mapper.xml的路徑。這里配置的目的是讓MyBatis在啟動(dòng)的時(shí)候去掃描這些映射器,創(chuàng)建映射關(guān)系。我們有四種指定Mapper文件的方式:
- ???使用相對(duì)于類路徑的資源引用(resource)
- ???使用完全限定資源定位符(URL)
- ? ?使用映射器接口實(shí)現(xiàn)類的完全限定類名
- ???將包內(nèi)的映射器接口實(shí)現(xiàn)全部注冊(cè)為映射器(最常用)
Mapper映射配置文件
一共有8個(gè)主要標(biāo)簽
cache – 給定命名空間的緩存配置(是否開(kāi)啟二級(jí)緩存)。
cache-ref – 其他命名空間緩存配置的引用。
resultMap – 用來(lái)描述如何從數(shù)據(jù)庫(kù)結(jié)果集中來(lái)加載對(duì)象 ?數(shù)據(jù)庫(kù)結(jié)果集和java對(duì)象的映射關(guān)系
sql – 可被其他語(yǔ)句引用的可重用語(yǔ)句塊。
增刪改查標(biāo)簽:
insert ,update ,delete ,select?
MyBatis的擴(kuò)展使用
動(dòng)態(tài)SQL
基于OGNL表達(dá)式,幫助我們更方便的拼接SQL
MyBatis的動(dòng)態(tài)標(biāo)簽主要有四類: if, choose(when,otherwise),trim (where, set),foreach。
if —— 用于做判斷,條件寫在test中
choose (when, otherwise) —— 用于做條件選擇
<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——會(huì)幫助我們?nèi)サ舳嘤嗟膚here,and,逗號(hào)之類的符號(hào),使用trim還可以指定獲取去掉前綴/后綴
where?元素只會(huì)在至少有一個(gè)子元素的條件返回 SQL 子句的情況下才去插入“WHERE”子句。而且,若語(yǔ)句的開(kāi)頭為“AND”或“OR”,where?元素也會(huì)將它們?nèi)コ?br /> 如果?where?元素沒(méi)有按正常套路出牌,我們可以通過(guò)自定義 trim 元素來(lái)定制?where?元素的功能。比如,和?where?元素等價(jià)的自定義 trim 元素為:
<trim prefix="WHERE" prefixOverrides="AND |OR ">... </trim>foreach —— 適用于需要遍歷集合的時(shí)候
<select id="selectPostIn" resultType="domain.blog.Post">SELECT *FROM POST PWHERE ID in<foreach item="item" index="index" collection="list"open="(" separator="," close=")">#{item}</foreach> </select>批量SQL
使用for each拼接長(zhǎng)SQL
缺點(diǎn):MySQL 的服務(wù)端對(duì)于接收的數(shù)據(jù)包有大小限制,max_allowed_packet 默認(rèn)是4M,需要修改對(duì)應(yīng)的參數(shù)配置
使用BatchExecutor進(jìn)行批操作
在我們的全局配置文件中,可以配置默認(rèn)的 Executor 的類型。其中有一種BatchExecutor
<setting name="defaultExecutorType" value="BATCH"/>
也可以在創(chuàng)建會(huì)話的時(shí)候指定執(zhí)行器類型
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
BatchExecutor底層是就是調(diào)用了JDBC的ps.addBatch()方法(在BatchExecutor調(diào)用了StatementHandler的batch(),在里面調(diào)用了statement的addBatch()方法)
嵌套(關(guān)聯(lián))查詢/ N+1 / 延遲加載
對(duì)于一對(duì)一的關(guān)聯(lián)查詢有兩種配置方式
嵌套結(jié)果:
?
嵌套查詢
<!-- 另一種聯(lián)合查詢(一對(duì)一)的實(shí)現(xiàn),但是這種方式有“N+1”的問(wèn)題 --><resultMap id="BlogWithAuthorQueryMap" type="com.chenpp.domain.associate.BlogAndAuthor"><id column="bid" property="bid" jdbcType="INTEGER"/><result column="name" property="name" jdbcType="VARCHAR"/><association property="author" javaType="com.chenpp.domain.Author"column="author_id" select="selectAuthor"/> <!-- selectAuthor 定義在下面--></resultMap><!-- 根據(jù)文章查詢作者,一對(duì)一,嵌套查詢,存在N+1問(wèn)題,可通過(guò)開(kāi)啟延遲加載解決 --><select id="selectBlogWithAuthorQuery" resultMap="BlogWithAuthorQueryMap" >select b.bid, b.name, b.author_id, a.author_id , a.author_namefrom blog bleft join author aon b.author_id=a.author_idwhere b.bid = #{bid, jdbcType=INTEGER}</select><!-- 嵌套查詢 --><select id="selectAuthor" parameterType="int" resultType="com.chenpp.domain.Author">select author_id authorId, author_name authorNamefrom author where author_id = #{authorId}</select>對(duì)于第二種嵌套查詢,因?yàn)槭欠謨纱尾樵兊?#xff0c;當(dāng)我們查詢了博客信息之后,會(huì)再發(fā)送一條SQL到數(shù)據(jù)庫(kù)查詢作者信息。如果查詢出N條博客記錄,那么就需要再執(zhí)行N次作者的SQL查詢 ?這就是所謂的N+1問(wèn)題
在MyBatis里面可以通過(guò)開(kāi)啟延遲加載的開(kāi)關(guān)來(lái)解決這個(gè)問(wèn)題
簡(jiǎn)單來(lái)說(shuō),lazyLoadingEnabled決定了是否延遲加載。
aggressiveLazyLoading決定了是不是對(duì)象的所有方法都會(huì)觸發(fā)查詢。
邏輯翻頁(yè)與物理翻頁(yè)
在我們查詢數(shù)據(jù)庫(kù)的操作中,有兩種翻頁(yè)方式,一種是邏輯翻頁(yè)(假分頁(yè)),一種是物理翻頁(yè)(真分頁(yè))。邏輯翻頁(yè)的原理是把所有數(shù)據(jù)查出來(lái),在內(nèi)存中刪選數(shù)據(jù)。 物理翻頁(yè)是真正的翻頁(yè),比如MySQL 使用limit ,Oracle使用rownum 。
邏輯翻頁(yè)
MyBatis 里面有一個(gè)邏輯分頁(yè)對(duì)象 RowBounds,里面主要有兩個(gè)屬性,offset 和limit(從第幾條開(kāi)始,查詢多少條).
我們可以在Mapper接口的方法上加上這個(gè)參數(shù), 而不需要修改xml里的SQL語(yǔ)句達(dá)到邏輯翻頁(yè)的效果。
物理翻頁(yè)
1.直接傳入?yún)?shù)(或者包裝一個(gè)page對(duì)象),在SQL語(yǔ)句中翻頁(yè)。
2.使用翻頁(yè)的插件,比如PageHelper,后面會(huì)介紹下MyBatis插件的原理和PageHelper的實(shí)現(xiàn)
簡(jiǎn)單地來(lái)說(shuō),就是根據(jù) PageHelper 的參數(shù),改寫我們的 SQL語(yǔ)句
?
?
總結(jié)
以上是生活随笔為你收集整理的MyBatis(一)MyBatis介绍和配置详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: MyBatis(五)MyBatis整合S
- 下一篇: MyBatis(六)SqlSession