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

核心配置文件: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"
        "//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的運行時行為

更多設置請參考://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配置文件必須在同一個包下

方式四:使用映射器介面實現類的完全限定名(因為基本不使用,在此不多贅述,如要學習請移步官方文檔)
//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()) {
  // 你的應用邏輯程式碼
}

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

Tags: