1. 程式人生 > >MyBatis入門——瞭解配置

MyBatis入門——瞭解配置

1、mybatis-config.xml

這個配置檔案的結構如下:

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

這裡我簡單說一說這些配置的意義,當然不可能每個都介紹的非常詳細,我說說一些比較實用和比較常用的吧。

properties

這個其實是很多元件都會提供的一個配置功能,在這裡可以可以定義一些屬性,那麼在這個檔案的後面都可以使用${propertyName}

來引用這個屬性,而且這裡還可以引用外部檔案,比如資料庫連線資訊,一般屬於高密配置,不能直接寫在配置檔案裡,那麼可以單獨寫個配置檔案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">
    <!--預設不開啟-->
    <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
    <!--預設就是:-->
    <property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="#|#"/>
    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
</properties>

那麼,在後續的配置中可以引用這些屬性:

<dataSource type="cn.dubby.mybatis.configuration.datasource.HikariCPDataSourceFactory">
    <!--since MyBatis 3.4.2 如果driver沒有找到,會使用冒號後的值作為預設值。此功能預設不開啟,開啟需指定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這兩個需要配合起來工作。作用是在使用${}引用屬性時,如果沒有找到這個屬性,可以使用一個預設值來代替,而屬性名和預設值中間的分隔符就是這裡配置的#|#,預設是:,使用時是${driver#|#com.mysql.cj.jdbc.Driver},意思就是沒有沒有配置driver,那就使用com.mysql.cj.jdbc.Driver

如果即使用<property name="propertyName" value="propertyValue"/>,又在config.properties配置了propertyName,那麼該以哪個為準呢?

config.properties裡配置的為準!

settings

先給出一個setting的配置選項吧,我們來感受一下:

<settings>
  <!--開啟或禁用快取-->
  <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"/>
  <!--資料庫裡下劃線自動匹配成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

這是類型別名,因為Java中類的全限定名是包名+類名,太長啦,所以這裡允許我們給他起個別名,這樣在後面使用的時候,可以簡短一點。

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

可以一個類一個類的指定;也可以直接指定一個package,這樣這個package下的所有的類都有別名了,此時你可以使用@Alias("blog")來修改他的別名,預設就是類名。

typeHandlers

在MyBatis中,資料庫中列轉化成Object中的變數,都是TypeHandler的轉換成果。他是一個介面:

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內建了很多TypeHandler,如果我們需要自定義呢?這裡給個簡單的例子,如果在資料庫中手機號儲存格式如下:

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 + "'";
    }
}

那麼,我們可以自定義一個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]);
    }
}

然後別忘了需要配置這個TypeHandler

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

那如果是列舉呢?

MyBatis給我們提供了兩種列舉相關的TypeHandler:EnumTypeHandlerEnumOrdinalTypeHandler。一看就知道,一個是根據名字轉換(String),一個是根據順序轉換(int)。

預設MyBatis是使用名字轉化的,也就是如果你的列舉是這樣的:

public enum CategoryEnum {
    A, B;
}

那麼資料庫中就存著A,B這樣的字串。但是很多DBA會建議我們使用Int來儲存(效能,空間等各方面考慮),那麼我們就需要使用EnumOrdinalTypeHandler了。

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

可是,如果還想用String轉換的方式呢,比如這種場景:

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

那可以在ResultMap在具體指定使用哪個來轉換:

<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開始的

objectFactory

MyBatis每次建立一個物件來承載查詢結果時,都會使用ObjectFactory來建立。我們可以重寫這個類,來實現我們的目的:

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給我們開放的一個祕密通道,我們可以通過各個外掛來攔截MyBatis生命週期裡比較重要的一些方法呼叫:

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

這裡給個例子吧:

@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("引數:{}", invocation.getArgs());
        Object result = invocation.proceed();
        logger.info("結果:{}", 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

先預覽一下:

<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>

首先我們看到一個environment,還有一個default="development"。這個作用是可以配置多個環境的資料來源,每個envonment有個名字,還可以設定一個預設的environment,在構建SqlSessionFactory的時候,可以指定envonment

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

如果不指定的話,就會使用預設的:

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

注意:一個SqlSessionFactory只能選擇一個environment

然後我們看到transactionManager,這個MyBatis提供了兩種內建的事務管理器,JDBCMANAGED。這裡不展開說,一般用JDBC就可以了,而且如果使用Spring整合的話,Spring會使用自己的transactionManager來替換MyBatis的transactionManager

然後就是DataSource了,MyBatis內建了三種:UNPOOLEDPOOLEDJNDI。這三個名字已經很明顯了,我就不解釋了,那如果想使用第三方的資料庫連線池呢,該如何處理,簡單的來說,可以自己實現DataSourceFactory,我這裡選擇了據說是最快的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,有這麼幾種選擇:

<!-- 使用相對路徑 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用絕對路徑 -->
<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,但卻又可以擺脫重複枯燥的jdbc拼接程式碼,讓我們開始瞭解他吧,他的可以配置項分為:

  • cache – 在當前namespcae配置一個快取
  • cache-ref – 從其他namespace引用一個快取.
  • resultMap – 定義瞭如何把從資料庫中查詢出來的資料封裝成Java的Object
  • parameterMap – Deprecated! 後續可能會刪除,這裡不解釋
  • sql – 可重複使用的一段SQL
  • insert – 插入
  • update – 更新
  • delete – 刪除
  • select – 選擇