Mybatis-應用分析和最佳實踐-核心配置

為什么要用Mybatis

  • 代碼重復
  • 結果集處理太復雜
  • 連接管理

常見的工具

  • DbUtils
    • 數據源的支持
    • QueryRunner
      • 直接使用QueryRunner來查詢
        • 提供了Resulthandler
        • 通過反射來做屬性的映射
  • JDBCTemplate
    • 提供了數據源的支持
    • RowMapper來使用結果的處理
      • RowMapper提供泛型,避免每次創(chuàng)建對應的實現類
        • mapRow

上述沒有解決的問題

  • 直接把SQL寫在了代碼里面
  • 條件只能按照順序傳入
  • 沒有實現實體類到數據庫記錄的映射
  • 沒有提供緩存等功能

ORM框架

  • O
    • 對象
  • M
    • 映射
  • R
    • 關系型數據庫


  • Hibernate
    • 直接利用session#save

問題

  • 不能指定部分字段
  • 無法自定義SQL,優(yōu)化困難
  • 不支持動態(tài)SQL

關于選型

  1. 業(yè)務簡單的項目用Hibernate
  2. 需要靈活的SQL,使用Mybatis
  3. 對于性能要求高,使用JDBC
  4. Spring JDBC 可以和ORM框架混用

Mybatis

特性

  1. 連接池對連接進行管理
  2. SQL和代碼分離,集中管理
  3. 參數映射和動態(tài)SQl
  4. 結果集映射
  5. 緩存管理
  6. 重復SQL的提取
  7. 插件機制

Mybatis編程式開發(fā)

  1. 導入依賴
  2. 指定配置
    1. mybatis-config.xml
    2. 讀取配置
  3. 通過會話工廠構建器SqlsessionFactoryBuild創(chuàng)建SqlsessionFactory
  4. 創(chuàng)建會話session
  5. 通過session獲取Mapper
    1. BlogMapper.class
    2. mappedstatement

核心對象作用域

作用域查看地址

SqlsessionFactoryBuilder

? 這個類可以被實例化、使用和丟棄,一旦創(chuàng)建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 實例的最佳作用域是方法作用域(也就是局部方法變量)。

? 你可以重用 SqlSessionFactoryBuilder 來創(chuàng)建多個 SqlSessionFactory實例,但最好還是不要一直保留著它,以保證所有的 XML 解析資源可以被釋放給更重要的事情。

SqlsessionFactory

? SqlSessionFactory 一旦被創(chuàng)建就應該在應用的運行期間一直存在,沒有任何理由丟棄它或重新創(chuàng)建另一個實例。

? 使用 SqlSessionFactory 的最佳實踐是在應用運行期間不要重復創(chuàng)建多次,多次重建 SqlSessionFactory 被視為一種代碼“壞習慣”。

? 因此 SqlSessionFactory 的最佳作用域是應用作用域。 有很多方法可以做到,最簡單的就是使用單例模式或者靜態(tài)單例模式。

SqlSession

? 每個線程都應該有它自己的 SqlSession 實例。

? SqlSession 的實例不是線程安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域。

? 絕對不能將 SqlSession 實例的引用放在一個類的靜態(tài)域,甚至一個類的實例變量也不行。 也絕不能將 SqlSession 實例的引用放在任何類型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你現在正在使用一種 Web 框架,考慮將 SqlSession 放在一個和 HTTP 請求相似的作用域中。 換句話說,每次收到 HTTP 請求,就可以打開一個 SqlSession,返回一個響應后,就關閉它。 這個關閉操作很重要,為了確保每次都能執(zhí)行關閉操作,你應該把這個關閉操作放到 finally 塊中。 下面的示例就是一個確保 SqlSession 關閉的標準模式:

try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的應用邏輯代碼
}

Mapper

? 映射器是一些綁定映射語句的接口。

? 映射器接口的實例是從 SqlSession 中獲得的。雖然從技術層面上來講,任何映射器實例的最大作用域與請求它們的 SqlSession 相同。但方法作用域才是映射器實例的最合適的作用域。

? 也就是說,映射器實例應該在調用它們的方法中被獲取,使用完畢之后即可丟棄。 映射器實例并不需要被顯式地關閉。

? 盡管在整個請求作用域保留映射器實例不會有什么問題,但是你很快會發(fā)現,在這個作用域上管理太多像 SqlSession 的資源會讓你忙不過來。 因此,最好將映射器放在方法作用域內。就像下面的例子一樣:

try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  // 你的應用邏輯代碼
}

核心配置

https://mybatis.org/mybatis-3/zh/configuration.html

Properties

? 這些屬性可以在外部進行配置,并可以進行動態(tài)替換。你既可以在典型的 Java 屬性文件中配置這些屬性,也可以在 properties 元素的子元素中設置。

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

設置(settings)

這是 MyBatis 中極為重要的調整設置,它們會改變 MyBatis 的運行時行為。具體細節(jié)參考文檔

https://mybatis.org/mybatis-3/zh/configuration.html#settings

完整的settings

<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"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

類型別名(typeAliases)

可以讓我們在使用的時候簡寫

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

使用的時候直接簡寫就行了

也可以直接指定包名

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>

內置類型別名

  • 基本數據類型(在前面+_)

    • _byte->byte
    • 。。。
  • 包裝類(直接小寫)

    • byte->Byte
  • 其他默認類型(全小寫)

    date Date
    decimal BigDecimal
    bigdecimal BigDecimal
    object Object
    map Map
    hashmap HashMap
    list List
    arraylist ArrayList
    collection Collection
    iterator Iterator

類型處理器

Java數據類型->數據庫類型

TypeHandlerRegistry

從代碼可以看到注冊了很多類型轉化器

public TypeHandlerRegistry() {
  register(Boolean.class, new BooleanTypeHandler());
  register(boolean.class, new BooleanTypeHandler());
  register(JdbcType.BOOLEAN, new BooleanTypeHandler());
  register(JdbcType.BIT, new BooleanTypeHandler());

  register(Byte.class, new ByteTypeHandler());
  register(byte.class, new ByteTypeHandler());
  register(JdbcType.TINYINT, new ByteTypeHandler());

  register(Short.class, new ShortTypeHandler());
  register(short.class, new ShortTypeHandler());
  register(JdbcType.SMALLINT, new ShortTypeHandler());

  register(Integer.class, new IntegerTypeHandler());
  register(int.class, new IntegerTypeHandler());
  register(JdbcType.INTEGER, new IntegerTypeHandler());

  register(Long.class, new LongTypeHandler());
  register(long.class, new LongTypeHandler());

  register(Float.class, new FloatTypeHandler());
  register(float.class, new FloatTypeHandler());
  register(JdbcType.FLOAT, new FloatTypeHandler());

  register(Double.class, new DoubleTypeHandler());
  register(double.class, new DoubleTypeHandler());
  register(JdbcType.DOUBLE, new DoubleTypeHandler());

  register(Reader.class, new ClobReaderTypeHandler());
  register(String.class, new StringTypeHandler());
  register(String.class, JdbcType.CHAR, new StringTypeHandler());
  register(String.class, JdbcType.CLOB, new ClobTypeHandler());
  register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
  register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
  register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
  register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
  register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
  register(JdbcType.CHAR, new StringTypeHandler());
  register(JdbcType.VARCHAR, new StringTypeHandler());
  register(JdbcType.CLOB, new ClobTypeHandler());
  register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
  register(JdbcType.NVARCHAR, new NStringTypeHandler());
  register(JdbcType.NCHAR, new NStringTypeHandler());
  register(JdbcType.NCLOB, new NClobTypeHandler());

  register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
  register(JdbcType.ARRAY, new ArrayTypeHandler());

  register(BigInteger.class, new BigIntegerTypeHandler());
  register(JdbcType.BIGINT, new LongTypeHandler());

  register(BigDecimal.class, new BigDecimalTypeHandler());
  register(JdbcType.REAL, new BigDecimalTypeHandler());
  register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
  register(JdbcType.NUMERIC, new BigDecimalTypeHandler());

  register(InputStream.class, new BlobInputStreamTypeHandler());
  register(Byte[].class, new ByteObjectArrayTypeHandler());
  register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
  register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
  register(byte[].class, new ByteArrayTypeHandler());
  register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
  register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
  register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
  register(JdbcType.BLOB, new BlobTypeHandler());

  register(Object.class, UNKNOWN_TYPE_HANDLER);
  register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
  register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);

  register(Date.class, new DateTypeHandler());
  register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
  register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
  register(JdbcType.TIMESTAMP, new DateTypeHandler());
  register(JdbcType.DATE, new DateOnlyTypeHandler());
  register(JdbcType.TIME, new TimeOnlyTypeHandler());

  register(java.sql.Date.class, new SqlDateTypeHandler());
  register(java.sql.Time.class, new SqlTimeTypeHandler());
  register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());

  // mybatis-typehandlers-jsr310
  if (Jdk.dateAndTimeApiExists) {
    this.register(Instant.class, InstantTypeHandler.class);
    this.register(LocalDateTime.class, LocalDateTimeTypeHandler.class);
    this.register(LocalDate.class, LocalDateTypeHandler.class);
    this.register(LocalTime.class, LocalTimeTypeHandler.class);
    this.register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class);
    this.register(OffsetTime.class, OffsetTimeTypeHandler.class);
    this.register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class);
    this.register(Month.class, MonthTypeHandler.class);
    this.register(Year.class, YearTypeHandler.class);
    this.register(YearMonth.class, YearMonthTypeHandler.class);
    this.register(JapaneseDate.class, JapaneseDateTypeHandler.class);
  }

  // issue #273
  register(Character.class, new CharacterTypeHandler());
  register(char.class, new CharacterTypeHandler());
}

BooleanTypeHandler

public class BooleanTypeHandler extends BaseTypeHandler<Boolean> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType)
    throws SQLException {
    ps.setBoolean(i, parameter);
  }

  @Override
  public Boolean getNullableResult(ResultSet rs, String columnName)
    throws SQLException {
    return rs.getBoolean(columnName);
  }

  @Override
  public Boolean getNullableResult(ResultSet rs, int columnIndex)
    throws SQLException {
    return rs.getBoolean(columnIndex);
  }

  @Override
  public Boolean getNullableResult(CallableStatement cs, int columnIndex)
    throws SQLException {
    return cs.getBoolean(columnIndex);
  }
}
  • 我們可以創(chuàng)建自己的類型轉化器
    • JDBC->JAVA
      • 列名來轉化結果
      • 下標來轉化結果
    • JAVA->JDBC類型
      • 參數設置

數據庫提供了Json類型,我們需要做轉化,就需要自己來寫自己的JSON類型

插入的時候,我們可以在語句指定TypeHandler

#{name,jdbcType=VARCHAR,typeHandler=com.gupaoedu.type.MyTypeHandler}

在ResultMap指定TypeHandler

<result column="name" property="name" jdbcType="VARCHAR" typeHandler="com.gupaoedu.type.MyTypeHandler"/>

對象工廠(objectFactory)

每次 MyBatis 創(chuàng)建結果對象的新實例時,它都會使用一個對象工廠(ObjectFactory)實例來完成實例化工作。

默認的對象工廠需要做的僅僅是實例化目標類,要么通過默認無參構造方法,要么通過存在的參數映射來調用帶有參數的構造方法。 如果想覆蓋對象工廠的默認行為,可以通過創(chuàng)建自己的對象工廠來實現。比如:

public class ExampleObjectFactory extends DefaultObjectFactory {
  public Object create(Class type) {
    return super.create(type);
  }
  public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
    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);
  }
}
<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>

插件(plugins)

環(huán)境配置(environments)

  • 我們通過環(huán)境可以用來區(qū)分不同環(huán)境的配置
  • 不過要記住:盡管可以配置多個環(huán)境,但每個 SqlSessionFactory 實例只能選擇一種環(huán)境。
<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>

Mappers

  • 導入的方式

類路徑的資源導入

<!-- 使用相對于類路徑的資源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

完全限定資源定位符URL

<!-- 使用完全限定資源定位符(URL) -->
<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>

我的筆記倉庫地址gitee 快來給我點個Star吧

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容