MyBatis入門(mén)——了解配置

原文鏈接:https://www.dubby.cn/detail.html?id=9094

1湾盒、mybatis-config.xml

這個(gè)配置文件的結(jié)構(gòu)如下:

  • properties
  • settings
  • typeAliases
  • typeHandlers
  • objectFactory
  • plugins
  • environments
    • environment
      • transactionManager
      • dataSource
  • databaseIdProvider
  • mappers

這里我簡(jiǎn)單說(shuō)一說(shuō)這些配置的意義饵撑,當(dāng)然不可能每個(gè)都介紹的非常詳細(xì)坐慰,我說(shuō)說(shuō)一些比較實(shí)用和比較常用的吧冈绊。

properties

這個(gè)其實(shí)是很多組件都會(huì)提供的一個(gè)配置功能,在這里可以可以定義一些屬性凿滤,那么在這個(gè)文件的后面都可以使用${propertyName}來(lái)引用這個(gè)屬性拔创,而且這里還可以引用外部文件,比如數(shù)據(jù)庫(kù)連接信息,一般屬于高密配置胖缤,不能直接寫(xiě)在配置文件里尚镰,那么可以單獨(dú)寫(xiě)個(gè)配置文件config.properties:

username=test
password=123456
url=jdbc:mysql://localhost:3306/mybatis_study?useUnicode=true&characterEncoding=UTF-8&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=false

然后在mybatis-config.xml里配置:

<properties resource="config.properties">
    <!--默認(rèn)不開(kāi)啟-->
    <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
    <!--默認(rèn)就是:-->
    <property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="#|#"/>
    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
</properties>

那么,在后續(xù)的配置中可以引用這些屬性:

<dataSource type="cn.dubby.mybatis.configuration.datasource.HikariCPDataSourceFactory">
    <!--since MyBatis 3.4.2 如果driver沒(méi)有找到哪廓,會(huì)使用冒號(hào)后的值作為默認(rèn)值狗唉。此功能默認(rèn)不開(kāi)啟,開(kāi)啟需指定org.apache.ibatis.parsing.PropertyParser.enable-default-value-->
    <property name="driverClassName" value="${driver#|#com.mysql.cj.jdbc.Driver}"/>
    <property name="jdbcUrl" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
</dataSource>

注意看涡真,org.apache.ibatis.parsing.PropertyParser.enable-default-valueorg.apache.ibatis.parsing.PropertyParser.default-value-separator這兩個(gè)需要配合起來(lái)工作分俯。作用是在使用${}引用屬性時(shí),如果沒(méi)有找到這個(gè)屬性哆料,可以使用一個(gè)默認(rèn)值來(lái)代替缸剪,而屬性名和默認(rèn)值中間的分隔符就是這里配置的#|#,默認(rèn)是:东亦,使用時(shí)是${driver#|#com.mysql.cj.jdbc.Driver}杏节,意思就是沒(méi)有沒(méi)有配置driver,那就使用com.mysql.cj.jdbc.Driver典阵。

如果即使用<property name="propertyName" value="propertyValue"/>奋渔,又在config.properties配置了propertyName,那么該以哪個(gè)為準(zhǔn)呢壮啊?

config.properties里配置的為準(zhǔn)嫉鲸!

settings

先給出一個(gè)setting的配置選項(xiàng)吧,我們來(lái)感受一下:

<settings>
  <!--開(kāi)啟或禁用緩存-->
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <!--數(shù)據(jù)庫(kù)里下劃線自動(dòng)匹配成Java中駝峰命名的屬性-->
  <setting name="mapUnderscoreToCamelCase" value="true"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods"
    value="equals,clone,hashCode,toString"/>
</settings>

typeAliases

這是類(lèi)型別名歹啼,因?yàn)镴ava中類(lèi)的全限定名是包名+類(lèi)名玄渗,太長(zhǎng)啦,所以這里允許我們給他起個(gè)別名狸眼,這樣在后面使用的時(shí)候捻爷,可以簡(jiǎn)短一點(diǎn)。

<typeAliases>
    <!--可以單獨(dú)指定別名-->
    <typeAlias type="cn.dubby.mybatis.configuration.entity.Blog" alias="Blog"/>
    <!--也可以直接指定包下所有類(lèi)的別名份企,此時(shí)還可以使用@Alias("blog")來(lái)修改他的別名-->
    <package name="cn.dubby.mybatis.configuration.entity"/>
</typeAliases>

可以一個(gè)類(lèi)一個(gè)類(lèi)的指定也榄;也可以直接指定一個(gè)package,這樣這個(gè)package下的所有的類(lèi)都有別名了司志,此時(shí)你可以使用@Alias("blog")來(lái)修改他的別名甜紫,默認(rèn)就是類(lèi)名。

typeHandlers

在MyBatis中骂远,數(shù)據(jù)庫(kù)中列轉(zhuǎn)化成Object中的變量囚霸,都是TypeHandler的轉(zhuǎn)換成果。他是一個(gè)接口:

public interface TypeHandler<T> {

  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

MyBatis內(nèi)置了很多TypeHandler激才,如果我們需要自定義呢拓型?這里給個(gè)簡(jiǎn)單的例子额嘿,如果在數(shù)據(jù)庫(kù)中手機(jī)號(hào)存儲(chǔ)格式如下:

image

但是在Java中,mobile是由countryCode和mobile共同決定的:

public class MobilePhone {

    private static final String DEFAULT_COUNTRY_CODE = "86";

    private String countryCode;

    private String mobile;

    public MobilePhone(String mobile) {
        this(DEFAULT_COUNTRY_CODE, mobile);
    }

    public MobilePhone(String countryCode, String mobile) {
        this.countryCode = countryCode;
        this.mobile = mobile;
    }

    @Override
    public String toString() {
        return "'" + countryCode + "_" + mobile + "'";
    }
}

那么劣挫,我們可以自定義一個(gè)MobilePhoneHandler:

@MappedJdbcTypes(JdbcType.VARCHAR)
public class MobilePhoneHandler extends BaseTypeHandler<MobilePhone> {

    public void setNonNullParameter(PreparedStatement ps, int i, MobilePhone parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter.toString());
    }

    public MobilePhone getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return convert(rs.getString(columnName));
    }

    public MobilePhone getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return convert(rs.getString(columnIndex));
    }

    public MobilePhone getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return convert(cs.getString(columnIndex));
    }

    private MobilePhone convert(String mobileInDB) {
        if (mobileInDB == null || mobileInDB.trim().length() == 0)
            return null;

        String[] mobilePair = mobileInDB.split("_");
        if (mobilePair.length != 2) {
            throw new IllegalArgumentException("mobile phone must be consist of countryCode and mobileNo.");
        }
        return new MobilePhone(mobilePair[0], mobilePair[1]);
    }
}

然后別忘了需要配置這個(gè)TypeHandler

<typeHandlers>
    <!--可以單獨(dú)指定別名-->
    <typeHandler handler="cn.dubby.mybatis.configuration.handler.MobilePhoneHandler"/>
    <!--也可以直接指定包下所有類(lèi)的別名-->
    <package name="cn.dubby.mybatis.configuration.handler"/>
</typeHandlers>

那如果是枚舉呢册养?

MyBatis給我們提供了兩種枚舉相關(guān)的TypeHandler:EnumTypeHandlerEnumOrdinalTypeHandler。一看就知道压固,一個(gè)是根據(jù)名字轉(zhuǎn)換(String)球拦,一個(gè)是根據(jù)順序轉(zhuǎn)換(int)。

默認(rèn)MyBatis是使用名字轉(zhuǎn)化的帐我,也就是如果你的枚舉是這樣的:

public enum CategoryEnum {
    A, B;
}

那么數(shù)據(jù)庫(kù)中就存著A坎炼,B這樣的字符串。但是很多DBA會(huì)建議我們使用Int來(lái)存儲(chǔ)(性能拦键,空間等各方面考慮)谣光,那么我們就需要使用EnumOrdinalTypeHandler了。

<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="cn.dubby.mybatis.configuration.enums.CategoryEnum"/>

可是芬为,如果還想用String轉(zhuǎn)換的方式呢萄金,比如這種場(chǎng)景:

public class Category {
    private Integer id;
    private CategoryEnum category1;
    private CategoryEnum category2;
}

那可以在ResultMap在具體指定使用哪個(gè)來(lái)轉(zhuǎn)換:

<resultMap id="CategoryResult" type="Category">
    <result column="id" property="id"/>
    <result column="category1" property="category1"/>
    <result column="category2" property="category2" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
</resultMap>

Enum順序是從0開(kāi)始的

objectFactory

MyBatis每次創(chuàng)建一個(gè)對(duì)象來(lái)承載查詢(xún)結(jié)果時(shí),都會(huì)使用ObjectFactory來(lái)創(chuàng)建碳柱。我們可以重寫(xiě)這個(gè)類(lèi),來(lái)實(shí)現(xiàn)我們的目的:

public class ExampleObjectFactory extends DefaultObjectFactory {

    private static final Logger logger = LoggerFactory.getLogger(ExampleObjectFactory.class);

    public <T> T create(Class<T> type) {
        logger.info("ExampleObjectFactory create a object, type is {}", type.getName());
        return super.create(type);
    }

    public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        logger.info("ExampleObjectFactory create a object, type is {}", type.getName());
        return super.create(type, constructorArgTypes, constructorArgs);
    }

    public void setProperties(Properties properties) {
        super.setProperties(properties);
    }

    public <T> boolean isCollection(Class<T> type) {
        return Collection.class.isAssignableFrom(type);
    }

}

plugins

插件是MyBatis給我們開(kāi)放的一個(gè)秘密通道熬芜,我們可以通過(guò)各個(gè)插件來(lái)攔截MyBatis生命周期里比較重要的一些方法調(diào)用:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

這里給個(gè)例子吧:

@Intercepts({@Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class ExamplePlugin implements Interceptor {

    private Logger logger = LoggerFactory.getLogger(ExamplePlugin.class);

    public Object intercept(Invocation invocation) throws Throwable {
        logger.info("====intercept start====");
        logger.info("參數(shù):{}", invocation.getArgs());
        Object result = invocation.proceed();
        logger.info("結(jié)果:{}", result);
        logger.info("====intercept complete====");
        return result;
    }

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
    }
}

配置插件:

<plugins>
    <plugin interceptor="cn.dubby.mybatis.configuration.plugin.ExamplePlugin">
        <property name="someProperty" value="100"/>
    </plugin>
</plugins>

environments

先預(yù)覽一下:

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <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>

首先我們看到一個(gè)environment莲镣,還有一個(gè)default="development"。這個(gè)作用是可以配置多個(gè)環(huán)境的數(shù)據(jù)源涎拉,每個(gè)envonment有個(gè)名字瑞侮,還可以設(shè)置一個(gè)默認(rèn)的environment,在構(gòu)建SqlSessionFactory的時(shí)候鼓拧,可以指定envonment

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);

如果不指定的話半火,就會(huì)使用默認(rèn)的:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);

注意:一個(gè)SqlSessionFactory只能選擇一個(gè)environment

然后我們看到transactionManager,這個(gè)MyBatis提供了兩種內(nèi)置的事務(wù)管理器季俩,JDBCMANAGED钮糖。這里不展開(kāi)說(shuō),一般用JDBC就可以了酌住,而且如果使用Spring集成的話店归,Spring會(huì)使用自己的transactionManager來(lái)替換MyBatis的transactionManager

然后就是DataSource了酪我,MyBatis內(nèi)置了三種:UNPOOLED消痛,POOLEDJNDI。這三個(gè)名字已經(jīng)很明顯了都哭,我就不解釋了秩伞,那如果想使用第三方的數(shù)據(jù)庫(kù)連接池呢逞带,該如何處理,簡(jiǎn)單的來(lái)說(shuō)纱新,可以自己實(shí)現(xiàn)DataSourceFactory展氓,我這里選擇了據(jù)說(shuō)是最快的HikariCP:

public class HikariCPDataSourceFactory implements DataSourceFactory {

    private HikariConfig config;

    @Override
    public void setProperties(Properties props) {
        config = new HikariConfig(props);
    }

    @Override
    public DataSource getDataSource() {
        return new HikariDataSource(config);
    }
}

然后修改DataSource配置:

<dataSource type="cn.dubby.mybatis.configuration.datasource.HikariCPDataSourceFactory">
    <property name="driverClassName" value="${driver#|#com.mysql.cj.jdbc.Driver}"/>
    <property name="jdbcUrl" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
</dataSource>

mappers

就是在這里配置所有的mapper,有這么幾種選擇:

<!-- 使用相對(duì)路徑 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用絕對(duì)路徑 -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用接口全限定名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 使用包名 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

2怒炸、XxxMapper.xml

mapper是MyBatis真正神奇的地方带饱,這里讓我們可以使用幾乎原生的sql,但卻又可以擺脫重復(fù)枯燥的jdbc拼接代碼阅羹,讓我們開(kāi)始了解他吧勺疼,他的可以配置項(xiàng)分為:

  • cache – 在當(dāng)前namespcae配置一個(gè)緩存
  • cache-ref – 從其他namespace引用一個(gè)緩存.
  • resultMap – 定義了如何把從數(shù)據(jù)庫(kù)中查詢(xún)出來(lái)的數(shù)據(jù)封裝成Java的Object
  • parameterMap – Deprecated! 后續(xù)可能會(huì)刪除,這里不解釋
  • sql – 可重復(fù)使用的一段SQL
  • insert – 插入
  • update – 更新
  • delete – 刪除
  • select – 選擇
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捏鱼,一起剝皮案震驚了整個(gè)濱河市执庐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌导梆,老刑警劉巖轨淌,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異看尼,居然都是意外死亡递鹉,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)藏斩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)躏结,“玉大人,你說(shuō)我怎么就攤上這事狰域∠彼” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵兆览,是天一觀的道長(zhǎng)屈溉。 經(jīng)常有香客問(wèn)我,道長(zhǎng)抬探,這世上最難降的妖魔是什么子巾? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮小压,結(jié)果婚禮上砰左,老公的妹妹穿的比我還像新娘。我一直安慰自己场航,他們只是感情好缠导,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著溉痢,像睡著了一般僻造。 火紅的嫁衣襯著肌膚如雪憋他。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,268評(píng)論 1 309
  • 那天髓削,我揣著相機(jī)與錄音竹挡,去河邊找鬼。 笑死立膛,一個(gè)胖子當(dāng)著我的面吹牛揪罕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宝泵,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼讲婚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼洛心!你這毒婦竟也來(lái)了暗甥?” 一聲冷哼從身側(cè)響起梗醇,我...
    開(kāi)封第一講書(shū)人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闯捎,沒(méi)想到半個(gè)月后椰弊,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓤鼻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年秉版,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茬祷。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡清焕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出牲迫,到底是詐尸還是另有隱情耐朴,我是刑警寧澤借卧,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布盹憎,位于F島的核電站,受9級(jí)特大地震影響铐刘,放射性物質(zhì)發(fā)生泄漏陪每。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一镰吵、第九天 我趴在偏房一處隱蔽的房頂上張望檩禾。 院中可真熱鬧,春花似錦疤祭、人聲如沸盼产。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)戏售。三九已至侨核,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間灌灾,已是汗流浹背搓译。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锋喜,地道東北人些己。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像嘿般,于是被迫代替她去往敵國(guó)和親段标。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容