Mybatis學習-配置、作用域和生命週期

月淺發表於2020-12-06

核心配置檔案:Mybatis-config.xml
Mybatis的配置檔案包含了會深深影響Mybatis行為的設定和屬性資訊

配置(configuration)

在mybatis-config.xml檔案中標籤都有規定的順序,需要按照以下順序新增
properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?

屬性(properties)

我們可以通過properties來實現引用檔案。Mybatis的一些屬性可以在外部進行配置,並可以進行動態替換。你既可以在典型的 Java 屬性檔案中配置這些屬性,也可以在properties元素的子元素中設定

  1. 編寫一個配置檔案(db.properties)
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/learn?serverTimezone=GMT&useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=123456789
  1. 在mybatis-config.xml中引入
<?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 resource="db.properties">
    	<property name="username" value="root"/>
        <property name="pwd" value="123456789"/>
    </properties>

    <environments default="test">
       
        <environment id="test">
            <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>

    <!--每一個Mapper.xml都需要在Mybatis核心配置檔案中註冊-->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>
  1. 測試
@Test
public void test() {
    //獲取SQLSession物件
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    try {

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.getUserList();

        for (User user : userList) {
           System.out.println(user);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        //關閉SQLSession
        sqlSession.close();
    }
}

Mybatis會先讀取properties元素體內的指定屬性,再根據resource屬性讀取類路徑下屬性檔案。所以外部配置檔案中編寫的配置會覆蓋內部標籤編寫的配置,外部配置檔案擁有更高優先順序

設定(setting)

這是Mybatis中極為重要的調整設定,他們會改變Mybatis的執行時行為

更多設定請參考:https://mybatis.org/mybatis-3/zh/configuration.html#settings

環境配置(environments)

Mybatis可以配置成適應多種環境
儘管可以配置多個環境,但每個SQLSessionFactory例項只能選擇一種環境
Mybatis預設的事務管理器就是JDBC,資料來源是POOLED

<environments default="test">	<!--需要使用的環境-->
    
    <!--環境一-->
    <environment id="development">		<!--id:每套environment的唯一標識-->
        <transactionManager type="JDBC"/>	<!--事務管理器的配置-->
        <dataSource type="POOLED">			<!--資料來源的配置-->
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/learn?serverTimezone=GMT&amp;useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
            <property name="username" value="root"/>
            <property name="password" value="123456789"/>
        </dataSource>
    </environment>

    <!--環境二-->
    <environment id="test">
        <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>

型別別名(typeAliases)

型別別名為Java型別設定一個短的名字,它僅用於XML配置,存在的意義僅在於用來減少類完全限定名的冗餘

<typeAliases>
    <typeAlias alias="User" type="com.luoqing.model.User"/>
</typeAliases>

也可以指定一個包名,Mybatis會在包名下搜尋需要的JavaBean

<typeAliases>
    <package name="com.luoqing.model" />
</typeAliases>

每一個在包名下的JavaBean,在沒有註解的情況下,會使用Bean首字母小寫的非限定類名來作為它的別名
如果有註解則其別名為其註解值

//這個類的別名為user
@Alias("user")
public class User {
    ……
}

在實體類比較少的時候,使用第一種方式。如果實體類十分多,建議使用第二種

型別處理器(typeHandlers)

Mybatis使用typeHandlers將資料庫中欄位的型別轉換為java類中屬性的型別,或將java類中屬性的型別轉換為資料庫中欄位的型別

通過Configuration物件獲取

@Test
public void myTest() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    TypeHandlerRegistry typeHandlerRegistry = sqlSession.getConfiguration().getTypeHandlerRegistry();
    Collection<TypeHandler<?>> list = typeHandlerRegistry.getTypeHandlers();
    System.out.println(list.size());
    for (TypeHandler<?> typeHandler : list) {
        System.out.println(typeHandler.getClass().getName());
    }

    sqlSession.close();

}

執行結果如下。以下所有的處理器類位於mybatis-X.X.X.jar/org/apache/ibatis/type中

40
org.apache.ibatis.type.InstantTypeHandler
org.apache.ibatis.type.MonthTypeHandler
org.apache.ibatis.type.JapaneseDateTypeHandler
org.apache.ibatis.type.UnknownTypeHandler
org.apache.ibatis.type.DateTypeHandler
org.apache.ibatis.type.CharacterTypeHandler
org.apache.ibatis.type.BigIntegerTypeHandler
org.apache.ibatis.type.SqlxmlTypeHandler
org.apache.ibatis.type.LocalDateTimeTypeHandler
org.apache.ibatis.type.ArrayTypeHandler
org.apache.ibatis.type.YearTypeHandler
org.apache.ibatis.type.FloatTypeHandler
org.apache.ibatis.type.NStringTypeHandler
org.apache.ibatis.type.BooleanTypeHandler
org.apache.ibatis.type.ByteTypeHandler
org.apache.ibatis.type.ClobTypeHandler
org.apache.ibatis.type.BigDecimalTypeHandler
org.apache.ibatis.type.ByteArrayTypeHandler
org.apache.ibatis.type.BlobTypeHandler
org.apache.ibatis.type.DateOnlyTypeHandler
org.apache.ibatis.type.YearMonthTypeHandler
org.apache.ibatis.type.SqlDateTypeHandler
org.apache.ibatis.type.OffsetTimeTypeHandler
org.apache.ibatis.type.LongTypeHandler
org.apache.ibatis.type.TimeOnlyTypeHandler
org.apache.ibatis.type.ZonedDateTimeTypeHandler
org.apache.ibatis.type.BlobInputStreamTypeHandler
org.apache.ibatis.type.LocalDateTypeHandler
org.apache.ibatis.type.IntegerTypeHandler
org.apache.ibatis.type.StringTypeHandler
org.apache.ibatis.type.BlobByteObjectArrayTypeHandler
org.apache.ibatis.type.ShortTypeHandler
org.apache.ibatis.type.NClobTypeHandler
org.apache.ibatis.type.LocalTimeTypeHandler
org.apache.ibatis.type.ClobReaderTypeHandler
org.apache.ibatis.type.SqlTimestampTypeHandler
org.apache.ibatis.type.SqlTimeTypeHandler
org.apache.ibatis.type.ByteObjectArrayTypeHandler
org.apache.ibatis.type.OffsetDateTimeTypeHandler
org.apache.ibatis.type.DoubleTypeHandler

Mybatis已經為我們寫好了40個型別處理器
其中DateTypeHandlers的原始碼如下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.type;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Date;

public class DateTypeHandler extends BaseTypeHandler<Date> {
    public DateTypeHandler() {
    }

    /**
    	獲取到對應的java型別的物件,將其轉換為jdbc型別
    */
    public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
        ps.setTimestamp(i, new Timestamp(parameter.getTime()));
    }

    /**
    	獲取到對應列的jdbc型別的物件,將其轉換為java型別
    */
    public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Timestamp sqlTimestamp = rs.getTimestamp(columnName);
        return sqlTimestamp != null ? new Date(sqlTimestamp.getTime()) : null;
    }

    /**
    	根據索引獲取到對應的資料的jdbc型別將其轉換為java型別
    */
    public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Timestamp sqlTimestamp = rs.getTimestamp(columnIndex);
        return sqlTimestamp != null ? new Date(sqlTimestamp.getTime()) : null;
    }

    /**
    	這個方法用在儲存過程中。將jdbc型別轉換為java型別
    */
    public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Timestamp sqlTimestamp = cs.getTimestamp(columnIndex);
        return sqlTimestamp != null ? new Date(sqlTimestamp.getTime()) : null;
    }
}

它的父類BaseTypeHandler<T>

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.type;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.executor.result.ResultMapException;
import org.apache.ibatis.session.Configuration;

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
    /** @deprecated */
    @Deprecated
    protected Configuration configuration;

    public BaseTypeHandler() {
    }

    /** @Deprecated表示此方法或者類不推薦使用,但是不代表不能用*/
    /** @deprecated */
    @Deprecated
    public void setConfiguration(Configuration c) {
        this.configuration = c;
    }

    
    public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null) {
            if (jdbcType == null) {
                throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
            }

            try {
                /** jdbcType是一個列舉型別 */
                /** 將對應的jdbc型別設定為null */
                ps.setNull(i, jdbcType.TYPE_CODE);
            } catch (SQLException var7) {
                throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. Cause: " + var7, var7);
            }
        } else {
            try {
                this.setNonNullParameter(ps, i, parameter, jdbcType);
            } catch (Exception var6) {
                throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . Try setting a different JdbcType for this parameter or a different configuration property. Cause: " + var6, var6);
            }
        }

    }

    public T getResult(ResultSet rs, String columnName) throws SQLException {
        try {
            return this.getNullableResult(rs, columnName);
        } catch (Exception var4) {
            throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + var4, var4);
        }
    }

    public T getResult(ResultSet rs, int columnIndex) throws SQLException {
        try {
            return this.getNullableResult(rs, columnIndex);
        } catch (Exception var4) {
            throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set.  Cause: " + var4, var4);
        }
    }

    public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
        try {
            return this.getNullableResult(cs, columnIndex);
        } catch (Exception var4) {
            throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement.  Cause: " + var4, var4);
        }
    }

    //定義了四個抽象方法,並在上面介面方法的實現中使用了它們
    //具體的實現交給子類去完成
    public abstract void setNonNullParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;

    public abstract T getNullableResult(ResultSet var1, String var2) throws SQLException;

    public abstract T getNullableResult(ResultSet var1, int var2) throws SQLException;

    public abstract T getNullableResult(CallableStatement var1, int var2) throws SQLException;
}

BaseTypeHandler是一個抽象類,也是型別處理器的基類。它作為TypeHandler介面的初步實現,實現了TypeHandler的四個方法;還另外定義了四個抽象方法,也就是DateTypeHandler中的四個方法。系統定義的40個TypeHandler方法都繼承自BaseTypeHandler

實現的介面TypeHandler<T>

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.type;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public interface TypeHandler<T> {
    void setParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;

    T getResult(ResultSet var1, String var2) throws SQLException;

    T getResult(ResultSet var1, int var2) throws SQLException;

    T getResult(CallableStatement var1, int var2) throws SQLException;
}

如果我們要自定義typeHandler時:

  • 繼承BaseTypeHandler類
  • 實現TypeHandler介面

一般不需要自定義,使用預設的就夠了

對映器(mappers)

MapperRegistry:註冊繫結我們的Mapper檔案

方式一:

<!-- 使用相對於類路徑的資源引用,在resource資料夾中 -->
<mappers>
  <mapper resource="com/luoqing/dao/UserMapper.xml"/>
</mappers>

方式二:使用class檔案繫結註冊

<!-- 使用對映器介面實現類的完全限定類名 -->
<mappers>
  <mapper class="com.luoqing.dao.UserMapper.xml"/>
</mappers>

注意:

  • 介面和它的Mapper配置檔案必須同名
  • 介面和它的Mapper配置檔案必須在同一個包下

方式三:

<!-- 將包內的對映器介面實現全部註冊為對映器 -->
<mappers>
  <package name="com.luoqing.dao"/>
</mappers>

注意:

  • 介面和它的Mapper配置檔案必須同名
  • 介面和它的Mapper配置檔案必須在同一個包下

方式四:使用對映器介面實現類的完全限定名(因為基本不使用,在此不多贅述,如要學習請移步官方文件)
https://mybatis.org/mybatis-3/zh/configuration.html#mappers

作用域(Scope)和生命週期

不同作用域和生命週期類別是至關重要的,因為錯誤的使用會導致非常嚴重的併發問題

SqlSessionFactoryBuilder

一旦建立了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 例項的最佳作用域是方法作用域(也就是區域性方法變數)。你可以重用 SqlSessionFactoryBuilder 來建立多個 SqlSessionFactory 例項,但最好還是不要一直保留著它,以保證所有的 XML 解析資源可以被釋放給更重要的事情。

SqlSessionFactory

SqlSessionFactory 一旦被建立就應該在應用的執行期間一直存在,沒有任何理由丟棄它或重新建立另一個例項。使用 SqlSessionFactory 的最佳實踐是在應用執行期間不要重複建立多次。因此 SqlSessionFactory 的最佳作用域是應用作用域。最簡單的就是使用單例模式或者靜態單例模式。

SqlSession

每個執行緒都應該有它自己的 SqlSession 例項。SqlSession 的例項不是執行緒安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域。絕對不能將 SqlSession 例項的引用放在一個類的靜態域,甚至一個類的例項變數也不行。 也絕不能將 SqlSession 例項的引用放在任何型別的託管作用域中,比如 Servlet 框架中的 HttpSession。每次收到 HTTP 請求,就可以開啟一個 SqlSession,返回一個響應後,就關閉它。這個關閉操作很重要,為了確保每次都能執行關閉操作,你應該把這個關閉操作放到 finally 塊中。下面的示例就是一個確保 SqlSession 關閉的標準模式:

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

在所有程式碼中都遵循這種使用模式,可以保證所有資料庫資源都能被正確地關閉。

相關文章