互联网轻量级框架整合之MyBatis配置详解

MyBatis核心配置文件mybatis-config.xml里有诸多配置项,但常用的就无非就如下这么多

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTDConfig3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- MyBatis配置文件 -->
<configuration>
    <properties></properties>
    <!-- 全局配置 -->
    <settings><setting name="" value=""/></settings>
    <!-- 类型别名配置 -->
    <typeAliases></typeAliases>
    <!-- 类型处理器配置 -->
    <typeHandlers></typeHandlers>
    <!-- 对象工厂配置 -->
    <objectFactory type=""></objectFactory>
    <!-- 插件配置 -->
    <plugins><plugin interceptor=""></plugin></plugins>
    <!-- 数据库环境配置 -->
    <environments default="development">
        <environment id="development">
            <!-- 事务管理器配置 -->
            <transactionManager type="JDBC"/>
            <!-- 数据源配置 -->
            <dataSource type="POOLED">
                <!-- 数据库连接池属性配置 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/ssm1?useUnicode=true;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="Ms123!@#"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 数据库ID提供者配置 -->
    <databaseIdProvider type=""></databaseIdProvider>
    <!-- 映射器配置 -->
    <mappers>
        <!-- XML映射文件路径配置 -->
        <mapper resource="mapper/RoleMapper.xml"/>
        <!-- Java映射器接口类路径配置 -->
        <mapper class="com.ssm.Dao.RoleDaoII"/>
    </mappers>
</configuration>

顺序不能颠倒,颠倒了顺序要飘红的,且项目启动也无法成功

properties属性用法

properties属性用于给系统配置运行参数,可以放在xml文件中,也可以放在properties文件中然后再引入到xml里,最差的方式是放在java代码中,如果放在java代码中,每次修改都需要重新编译,但在某些场景下又不得不用,比如配置的内容需要进行加解密等等,存在中间转换过程


<!-- 配置文件开始 -->
<configuration>
    <!-- 配置属性 -->
    <properties>
        <property name="database.driver" value="com.mysql.cj.jdbc.Driver"/> <!-- 数据库驱动 -->
        <property name="database.url" value="jdbc:mysql://localhost:3306/ssm?useUnicode=true;chatacterEncoding=utf8"/> <!-- 数据库连接URL -->
        <property name="database.username" value="root"/> <!-- 数据库用户名 -->
        <property name="database.password" value="Ms123!@#"/> <!-- 数据库密码 -->
    </properties>
    <!-- 设置 -->
    <settings><setting name="" value=""/></settings>
    <!-- 类型别名 -->
    <typeAliases></typeAliases>
    <!-- 类型处理器 -->
    <typeHandlers></typeHandlers>
    <!-- 对象工厂配置 -->
    <objectFactory type=""></objectFactory>
    <!-- 插件配置 -->
    <plugins><plugin interceptor=""></plugin></plugins>
    <!-- 数据库环境配置,默认环境为development -->
    <environments default="development">
        <environment id="development">
            <!-- 事务管理器配置 -->
            <transactionManager type="JDBC"/>
            <!-- 数据源配置,使用连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="${database.driver}"/> <!-- 驱动 -->
                <property name="url" value="${database.url}"/> <!-- URL -->
                <property name="username" value="${database.username}"/> <!-- 用户名 -->
                <property name="password" value="${database.password}"/> <!-- 密码 -->
            </dataSource>
        </environment>
    </environments>
    <!-- 数据库ID提供者配置 -->
    <databaseIdProvider type=""></databaseIdProvider>
    <!-- 映射器配置 -->
    <mappers>
        <mapper resource="mapper/RoleMapper.xml"/> <!-- XML映射文件路径 -->
        <mapper class="com.ssm.Dao.RoleDaoII"/> <!-- 映射接口类路径 -->
    </mappers>
</configuration>
        <!-- 配置文件结束 -->

编写如下配置内容到config.properties文件中,并将文件放在resources路径下

# 此段为配置文件内容,而非函数或类代码,故按行进行注释解释

# 指定MySQL连接的驱动类
className=com.mysql.cj.jdbc.Driver

# 设置数据库连接的URL,包括数据库地址、端口和数据库名称等信息
url=jdbc:mysql://localhost:3306/ssm1?useUnicode=true&characterEncoding=utf8

# 指定数据库的用户名
username=root

# 指定数据库的密码
password=Ms123!@#

然后修改xml文件中的properties的配置属性,将配置文件配置进去


<!-- 配置文件开始 -->
<configuration>
    <!-- 配置属性 -->
    <properties resource="config.properties"/>
    <!-- 设置 -->
    <settings><setting name="" value=""/></settings>
    <!-- 类型别名 -->
    <typeAliases></typeAliases>
    <!-- 类型处理器 -->
    <typeHandlers></typeHandlers>
    <!-- 对象工厂配置 -->
    <objectFactory type=""></objectFactory>
    <!-- 插件配置 -->
    <plugins><plugin interceptor=""></plugin></plugins>
    <!-- 数据库环境配置,默认环境为development -->
    <environments default="development">
        <environment id="development">
            <!-- 事务管理器配置 -->
            <transactionManager type="JDBC"/>
            <!-- 数据源配置,使用连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="${database.driver}"/> <!-- 驱动 -->
                <property name="url" value="${database.url}"/> <!-- URL -->
                <property name="username" value="${database.username}"/> <!-- 用户名 -->
                <property name="password" value="${database.password}"/> <!-- 密码 -->
            </dataSource>
        </environment>
    </environments>
    <!-- 数据库ID提供者配置 -->
    <databaseIdProvider type=""></databaseIdProvider>
    <!-- 映射器配置 -->
    <mappers>
        <mapper resource="mapper/RoleMapper.xml"/> <!-- XML映射文件路径 -->
        <mapper class="com.ssm.Dao.RoleDaoII"/> <!-- 映射接口类路径 -->
    </mappers>
</configuration>
        <!-- 配置文件结束 -->

如果需要进行解密再进行后续动作,则要放到Java代码中进行, 这里假设存在CodeUtils.decode()解密方法

String cfgFile="mybatis-config.xml";
InputStream inputStream;
InputStream in = Resources.getResourceAsStream("jdbc.properties");
Properties props = new Properties();
props.load(in);
String username = props.getProperty("database.username");
String password = props.getProperty("database.password");
//解密用户名密码,并在属性中重置
props.put("database.username", CodeUtils.decode(username));
props.put("database.password", CodeUtils.decode(password));
SqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, props);

settings属性用法

在MyBatis的配置文件中, 标签用于定义一系列全局的配置选项,这些选项能够影响MyBatis的核心行为。以下是一些常见的settings配置项及其解释:

  • cacheEnabled:是否开启全局二级缓存。当设置为 true 时,允许所有映射器配置文件中已经配置的任何缓存。这对于需要跨会话共享数据、减少数据库查询的场景非常有用。默认值通常为 true。
  • lazyLoadingEnabled:延迟加载开关。设置为 true 时,启用延迟加载特性,即在首次访问关联对象时才进行实际查询。否则,关联对象会在查询主对象时一并加载(急加载)。默认值可能因版本而异,一般默认为 false。
  • aggressiveLazyLoading:当启用时候,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之每种属性将按需加载,在3.4.1版本之前默认为true,之后的版本默认为false。
  • multipleResultSetsEnabled:是否允许单个SQL语句返回多个结果集。如果设置为 true,则MyBatis会尝试处理具有多个结果集的查询。这要求数据库驱动程序和JDBC连接支持多结果集。默认值通常为 true。
  • useColumnLabel:指定MyBatis是否使用JDBC列标签(ResultSetMetaData.getColumnName())还是列名(ResultSetMetaData.getTableName() + ‘.’ + ResultSetMetaData.getColumnName())来获取字段名。不同驱动可能有不同的行为,建议根据所使用的数据库驱动查阅相关文档或进行测试以确定最佳设置。默认值通常为 true,即使用列标签。
  • useGeneratedKeys:是否启用JDBC的自动增长键支持。当设置为 true 时,MyBatis将尝试使用JDBC的getGeneratedKeys方法来获取插入操作生成的主键。这适用于那些支持自动生成键的数据库系统。默认值通常为 false。
  • defaultStatementTimeout:设置默认的超时时间(以秒为单位),用于控制数据库操作的执行时间。若超过指定时间仍未完成,MyBatis将抛出异常。默认值通常为未设置(无限制)。
  • autoMappingBehavior:自动映射行为。可选值有 NONE、PARTIAL 和 FULL。NONE 表示完全关闭自动映射;PARTIAL 只映射没有明确定义的简单结果集;FULL 试图映射所有列到对象属性,包括嵌套的结果。默认值可能为 PARTIAL。
  • autoMappingUnknownColumnBehavior:指定自动映射遇到SQL未知字段名(或未知属性类型)时的行为,默认配置是NONE表示不处理,只有当日志级别为WARN或者以下时,才会显示相关日志,如果处理失败则会抛出SqlSessionException,可选值有NONE、WARNING、FAILING。
  • defaultExecutorType:设置默认的执行器类型。可选值有 SIMPLE(简单执行器)、REUSE(重用执行器)和 BATCH(批处理执行器)。不同的执行器在处理批量操作、缓存行为等方面有所不同。默认值通常为 SIMPLE。
  • defaultFetchSize:设置数据库驱动程序默认返回的条数限制,默认为任何整数。
  • safeRowBoundsEnabled:是有允许在语句中使用分页(RowBounds),如果允许则设置为false,默认值为true。
  • safeResultHandlerEnabled:是否允许在语句中使用分页(ResultHandler),如果允许则设置为false,默认值为true。
  • mapUnderscoreToCamelCase:是否开启驼峰命名自动映射。如果设置为 true,MyBatis会将数据库中的下划线命名风格的列名自动映射到Java对象中驼峰命名风格的属性名。默认值通常为 false。
  • localCacheScope:定义本地缓存作用域。可选值有 SESSION 和 STATEMENT。SESSION 表示在一个用户会话中缓存结果,STATEMENT 则表示仅在当前执行的语句中缓存结果。默认值通常为 SESSION
  • jdbcTypeForNull:当没有为参数提供特定的JDBC类型时,为空值指定JDBC类型,某些驱动需要指定列的JDBC类型,多数情况下直接用一般类型即可,可选项有NULL、VARCHAR、OTHER,默认值为OTHER
  • lazyLoadTriggerMethods:指定哪个对象的方法触发一次延迟加载,默认值equals、clone、hashCode、toString
  • defaultScriptinglanguage:指定生成SQL的默认语言驱动,默认值org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver
  • callSettersOnNulls:指定当结果集中值为null时,是否调用映射对象的settet(map对象时为put)方法,这对于有Map.keySet方法依赖或者null值初始化时是有用的。注意基本类型int、boolean等不能设置成null。
  • logPrefix:指定MyBatis增加到日志名称的前缀,默认Not Set。
  • logImpl:指定MyBatis所用日志的具体实现,如未指定则自动查找,配置项SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING,默认为Not Set。
  • proxyFactory:指定MyBatis构建具有延迟加载能力的对象所用到的代理工具。
  • vfsImpl:指定VFS的实现类,配置该项需提供VFS的全限定名,如果存在多个需逗号隔开,默认Not Set。
  • useActualParamName:是否允许用方法参数中生命的实际名称引用参数,若要使用此功能项目须采用Java8或者以上版本编译并且加上"parameters"选项,默认值为true。

以上是一些常见的settings配置项,实际上MyBatis提供了更多可配置选项。具体配置时,请参照MyBatis官方文档以获取最新的、详尽的配置说明以及默认值信息。记得根据项目需求和数据库环境来合理调整这些配置。

    <settings>
        <!-- 缓存是否启用,默认为true -->
        <setting name="cacheEnabled" value="true"/>
        <!-- 是否启用延迟加载,默认为true -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 是否支持多个结果集,默认为true -->
        <setting name="multipleResultSetsEnabled" value="true"/>
        <!-- 是否使用列标签代替列名,默认为true -->
        <setting name="useColumnLabel" value="true"/>
        <!-- 是否使用生成的键,默认为false -->
        <setting name="useGeneratedKeys" value="false"/>
        <!-- 自动映射行为,默认为PARTIAL -->
        <setting name="autoMappingBehavior" value="PARTIAL"/>
        <!-- 自动映射未知列的行为,默认为WARNING -->
        <setting name="autoMappingUnKnownColumnBehavior" value="WARNING"/>
        <!-- 默认的执行器类型,默认为SIMPLE -->
        <setting name="defaultExecutorType" value="SIMPLE"/>
        <!-- 默认的语句超时时间,默认为25000毫秒 -->
        <setting name="defaultStatementTimeout" value="25000"/>
        <!-- 默认的获取大小,默认为100 -->
        <setting name="defaultFetchSize" value="100"/>
        <!-- 是否安全的使用RowBounds,默认为false -->
        <setting name="safeRowBoundsEnabled" value="false"/>
        <!-- 是否将下划线映射到驼峰命名,默认为false -->
        <setting name="mapUnderscoreToCamelCase" value="false"/>
        <!-- 本地缓存的范围,默认为SESSION -->
        <setting name="localCacheScope" value="SESSION"/>
        <!-- null值的JDBC类型,默认为OTHER -->
        <setting name="jdbcTypeForNull" value="OTHER"/>
        <!-- 延迟加载触发的方法,默认为equals,clone,toString,hashCode -->
        <setting name="lazyLoadTriggerMethods" value="equals,clone,toString,hashCode,"/>
    </settings>

typeAliases属性用法

在MyBatis中, 配置元素位于MyBatis的核心配置文件(通常为mybatis-config.xml)中,用于定义类型别名(Type Aliases)。类型别名是为Java类提供一个更简洁、易记的名字,以便在MyBatis的映射文件中使用。这样做有以下几个好处:
简化XML映射文件:在编写SQL映射语句时,可以直接使用类型别名代替完整的类名,使得XML文件更加简洁且易于阅读。
避免全限定类名:无需每次都书写类的全限定名(包括包名),特别是对于较长的类名或包名结构复杂的项目,可以显著减少冗余字符。
提高可维护性:如果类名或包名发生变化,只需要在一处(即配置)更新别名即可,无需修改所有引用该类的映射文件。

用于为单个Java类型定义别名。基本语法如下:

<typeAliases>
  <typeAlias alias="AliasName" type="FullyQualifiedClassName"/>
</typeAliases>

通过 元素自动扫描指定包下的所有类,并为它们生成别名。别名通常是去掉包名后的类名(首字母小写)。这种方式可以快速为大量类创建别名,而无需逐一声明,在MyBatis的核心配置文件(如mybatis-config.xml)中的 元素内,添加 子元素。其基本语法如下:

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

这里的 name 属性指定了需要扫描的Java包名。MyBatis将查找此包及其子包下所有的非抽象公共类(public non-abstract classes),并为它们生成类型别名。别名生成规则:

  • 去掉包名:仅保留类名部分,即从全限定类名中去除包路径(包括点号)。
  • 首字母小写:将类名的第一个字母转换为小写。如果类名以大写字母开头,其余部分保持不变;如果类名以小写字母开头,则整个类名保持原样。

例如,对于包com.example.model下的两个类:com.example.model.Customercom.example.model.Order对应的类型别名分别为:customer和order,然后遍可以在MyBatis的映射文件(.xml 文件)中,可以使用生成的别名代替原本的全限定类名。例如:

<mapper namespace="com.example.mapper.CustomerMapper">
  <resultMap id="customerResultMap" type="customer">
    <!-- 结果映射的其他属性和元素 -->
  </resultMap>

  <select id="selectCustomerById" parameterType="int" resultMap="customerResultMap">
    SELECT * FROM customer WHERE id = #{id}
  </select>
</mapper>

在这个例子中, 的 type 属性和 的 parameterType 属性均使用了由 自动扫描生成的别名(customer 和 int)。

注意事项:

  • 避免冲突:由于别名是基于类名生成的,因此在同一个包或子包下应避免类名首字母相同但后续部分不同的情况,以防止生成相同的别名导致冲突。如果确实存在这种情况,建议使用 元素为这类类手动指定别名。
  • 包扫描范围: 元素只会扫描指定包及其子包下的类。如果需要扫描多个不相关的包,需要为每个包单独添加一个 元素。
  • 内部类和匿名类: 扫描不会处理内部类(包括静态内部类)和匿名类,因为这些类通常不符合作为实体类进行数据持久化的场景。

通过 元素自动扫描指定包下的所有类并生成别名,极大地简化了大量类型别名的配置工作,尤其适用于拥有大量实体类且遵循统一命名规范的项目。只需在配置文件中指定包名,即可快速为这些类创建易于使用的别名,提高映射文件的可读性和维护性

在Java类上使用 @Alias 注解直接定义别名,无需在XML配置中显式声明。这与前两种方式相比,将别名定义与类本身紧密关联,提高了代码的内聚性。

import org.apache.ibatis.type.Alias;

@Alias("CustomUser")
public class User {
    // 类的属性和方法...
}

在这个例子中,User 类通过 @Alias 注解被赋予了别名 CustomUser,在映射文件中可以直接使用 CustomUser 代替 User 类名。

综上所述, 配置项在MyBatis中扮演着简化映射文件、提升代码可读性和维护性的重要角色,通过三种不同的方式(单独声明、包扫描、注解)为Java类型定义简短的别名,使得在编写SQL映射时能够以更简洁的方式引用这些类型

在MyBatis中别名使用过注册机TypeAliasRegistry(org.apache.ibatis.type.TypeAliasRegistry)管理的,SqlSessionFactory提供了一个 getConfiguration()方法,可以获取到Configuration对象,而TypeAliasRegistry就是Configuration类的一个成员。通过Configuration,您可以操作TypeAliasRegistry,如下代码所示方式

sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSessionFactory.getConfiguration().getTypeAliasRegistry().registerAlias("Role", Role.class);

实际上如果看源码,就会找到默认已经注册好的系统别名有很多
在这里插入图片描述

注意:在MyBatis中别名是不区分大小写的

typeHandler属性用法

MyBatis内置typeHandler

在JDBC中,需要在PreparedStatement对象中设置预编译的SQL语句的参数,在执行SQL语句后,会通过ResultSet对象获取数据库的数据映射到POJO上,这样就存在一个类型转换关系,也就是数据库类型和Java类型之间的转换关系,而typeHandler就是干这个事的,在typeHandler中,分为jdbcType和javaType,它负责完整之间的转换,在大多数场景下,并不需要配置typeHandler,MyBatis会探测应该使用什么类型的typeHandler处理,特殊场景下MyBatis也允许自定义typeHandler来处理

类型处理器JavaTypeJDBCType
BooleanTypeHandlerjava.lang.Boolean, boolean数据库兼容的BOOLEAN
ByteTypeHandlerjava.lang.Byte, byte数据库兼容的NUMERIC或BYTE
ShortTypeHandlerjava.lang.Short, short数据库兼容的NUMERIC或SHORT INTEGER
IntegerTypeHandlerjava.lang.Integer, int数据库兼容的NUMERIC或INTEGER
LongTypeHandlerjava.lang.Long, long数据库兼容的NUMERIC或LONG INTEGER
FloatTypeHandlerjava.lang.Float, float数据库兼容的NUMERIC或FLOAT
DoubleTypeHandlerjava.lang.Double, double数据库兼容的NUMERIC或DOUBLE
BigDecimalTypeHandlerjava.math.BigDecimal数据库兼容的NUMERIC或DECIMAL
StringTypeHandlerjava.lang.StringCHAR、VARCHAR
ClobReaderTypeHandlerjava.io.Reader
ClobTypeHandlerjava.lang.StringCLOB、LONGVARCHAR
NStringTypeHandlerjava.lang.StringNVARCHAR、NCHAR
NClobTypeHandlerjava.lang.StringNCLOB
BlobInputStreamTypeHandlerjava.io.InputStream
ByteArrayTypeHandlerbyte[]数据库兼容的字节流类型
BlobTypeHandlerbyte[]BLOB、LONGVARBINARY
DateTypeHandlerjava.util.DateTIMESTAMP
DateOnlyTypeHandlerjava.util.DateDATE
TimeOnlyTypeHandlerjava.util.DateTIME
SqlTimestampTypeHandlerjava.sql.TimestampTIMESTAMP
SqlDateTypeHandlerjava.sql.DateDATE
SqlTimeTypeHandlerjava.sql.TimeTIME
ObjectTypeHandlerAnyOTHER或未指定类型
EnumTypeHandlerEnumerationType任何兼容VARCHAR的字符串类型,存储枚举的名称(而不是索引)
EnumOrdinalTypeHandlerEnumerationType任何兼容的NUMERIC或DOUBLE类型,存储枚举的索引(而不是名称)

MyBatis系统内部定义了许多有用的typeHandler,在大部分情况下无需显式的生命jdbcType和javaType,也无需用typeHandler属性指定对应的typeHandler实现数据类型转换,MyBatis会自己完成探测和转换,但有些时候需要修改一些规则,比如枚举类往往需要编写规则

在使用typeHandler前,参考MyBatis自身的typeHandler是如何实现的
在这里插入图片描述
从图中不难看出,源码中有个BaseTypeHandler抽象类实现了TypeHandler接口,而具体的TypeHandler继承了这个抽象类,不难看出这个抽象类是typeHandler机制中的一个通用父类,分别看一下源码

/**
 * TypeHandler接口用于处理Java类型到JDBC类型的转换。
 * 它是MyBatis中非常重要的组件之一,负责数据的转换。
 *
 * @param <T> 要处理的Java数据类型
 */
public interface TypeHandler<T> {
    
    /**
     * 将Java类型的数据设置到PreparedStatement中。
     * 
     * @param preparedStatement 预编译的SQL语句对象
     * @param index 参数在PreparedStatement中的索引
     * @param value 要设置的Java类型的参数值
     * @param jdbcType 与参数对应的JDBC类型
     * @throws SQLException 如果设置参数时发生SQL异常
     */
    void setParameter(PreparedStatement preparedStatement, int index, T value, JdbcType jdbcType) throws SQLException;

    /**
     * 从ResultSet中获取结果数据,并转换为指定的Java类型。
     * 
     * @param resultSet 从数据库查询结果生成的结果集
     * @param columnName 列的名称
     * @return 转换后的Java类型数据
     * @throws SQLException 如果从结果集中获取数据时发生SQL异常
     */
    T getResult(ResultSet resultSet, String columnName) throws SQLException;

    /**
     * 从ResultSet中获取结果数据,并转换为指定的Java类型。
     * 
     * @param resultSet 从数据库查询结果生成的结果集
     * @param index 列的索引
     * @return 转换后的Java类型数据
     * @throws SQLException 如果从结果集中获取数据时发生SQL异常
     */
    T getResult(ResultSet resultSet, int index) throws SQLException;

    /**
     * 从CallableStatement中获取结果数据,并转换为指定的Java类型。
     * 
     * @param callableStatement 可调用的SQL语句对象
     * @param index 列的索引
     * @return 转换后的Java类型数据
     * @throws SQLException 如果从结果集中获取数据时发生SQL异常
     */
    T getResult(CallableStatement callableStatement, int index) throws SQLException;
}

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;

/**
 * BaseTypeHandler是TypeHandler的抽象基类,提供了类型处理的通用实现。
 * 它定义了将Java类型数据设置到PreparedStatement中,以及从ResultSet或CallableStatement中获取Java类型数据的方法。
 * 具体的类型处理逻辑需要由子类通过覆盖抽象方法来实现。
 *
 * @param <T> 要处理的Java类型
 */
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
    /** 
     * 配置对象,提供MyBatis的配置信息。已弃用,建议使用其他方式获取配置信息。
     */
    @Deprecated
    protected Configuration configuration;

    /**
     * 构造函数,无参数。
     */
    public BaseTypeHandler() {
    }

    /** 
     * 设置配置对象。已弃用,建议使用其他方式设置配置信息。
     * 
     * @param c MyBatis的配置对象。
     */
    @Deprecated
    public void setConfiguration(Configuration c) {
        this.configuration = c;
    }

    /**
     * 将参数设置到PreparedStatement中。
     * 
     * @param ps PreparedStatement对象。
     * @param i 参数在PreparedStatement中的位置。
     * @param parameter 要设置的参数值。
     * @param jdbcType 参数对应的JdbcType类型。
     * @throws SQLException 如果设置参数时发生SQL异常。
     * @throws TypeException 如果处理参数时发生类型转换异常。
     */
    public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null) {
            // 处理参数为null的情况
            if (jdbcType == null) {
                throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
            }

            try {
                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 {
            // 处理参数不为null的情况
            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);
            }
        }
    }

    /**
     * 从ResultSet中获取一个结果值。
     * 
     * @param rs ResultSet对象。
     * @param columnName 列的名称。
     * @return 返回从ResultSet中获取的值。
     * @throws SQLException 如果从ResultSet中获取值时发生SQL异常。
     * @throws ResultMapException 如果处理结果时发生异常。
     */
    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);
        }
    }

    /**
     * 从ResultSet中根据列索引获取一个结果值。
     * 
     * @param rs ResultSet对象。
     * @param columnIndex 列的索引。
     * @return 返回从ResultSet中获取的值。
     * @throws SQLException 如果从ResultSet中获取值时发生SQL异常。
     * @throws ResultMapException 如果处理结果时发生异常。
     */
    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);
        }
    }

    /**
     * 从CallableStatement中根据列索引获取一个结果值。
     * 
     * @param cs CallableStatement对象。
     * @param columnIndex 列的索引。
     * @return 返回从CallableStatement中获取的值。
     * @throws SQLException 如果从CallableStatement中获取值时发生SQL异常。
     * @throws ResultMapException 如果处理结果时发生异常。
     */
    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);
        }
    }

    /**
     * 设置非空参数到PreparedStatement中。
     * 这个方法需要由子类实现,提供具体的类型转换逻辑。
     * 
     * @param ps PreparedStatement对象。
     * @param i 参数在PreparedStatement中的位置。
     * @param parameter 要设置的参数值,非null。
     * @param jdbcType 参数对应的JdbcType类型。
     * @throws SQLException 如果设置参数时发生SQL异常。
     */
    public abstract void setNonNullParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;

    /**
     * 从ResultSet中获取一个可能为null的结果值。
     * 这个方法需要由子类实现,提供具体的类型转换逻辑。
     * 
     * @param rs ResultSet对象。
     * @param columnName 列的名称。
     * @return 返回从ResultSet中获取的值,可能为null。
     * @throws SQLException 如果从ResultSet中获取值时发生SQL异常。
     */
    public abstract T getNullableResult(ResultSet var1, String var2) throws SQLException;

    /**
     * 从ResultSet中根据列索引获取一个可能为null的结果值。
     * 这个方法需要由子类实现,提供具体的类型转换逻辑。
     * 
     * @param rs ResultSet对象。
     * @param columnIndex 列的索引。
     * @return 返回从ResultSet中获取的值,可能为null。
     * @throws SQLException 如果从ResultSet中获取值时发生SQL异常。
     */
    public abstract T getNullableResult(ResultSet var1, int var2) throws SQLException;

    /**
     * 从CallableStatement中根据列索引获取一个可能为null的结果值。
     * 这个方法需要由子类实现,提供具体的类型转换逻辑。
     * 
     * @param cs CallableStatement对象。
     * @param columnIndex 列的索引。
     * @return 返回从CallableStatement中获取的值,可能为null。
     * @throws SQLException 如果从CallableStatement中获取值时发生SQL异常。
     */
    public abstract T getNullableResult(CallableStatement var1, int var2) throws SQLException;
}

/**
 * 字符串类型处理器,继承自BaseTypeHandler,用于处理Java String类型与SQL字符串类型的转换。
 */
package org.apache.ibatis.type;

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

public class StringTypeHandler extends BaseTypeHandler<String> {
    
    /**
     * 构造函数
     */
    public StringTypeHandler() {
    }

    /**
     * 设置非空参数。
     * @param ps PreparedStatement对象
     * @param i 参数在PreparedStatement中的位置
     * @param parameter 需要设置的参数值
     * @param jdbcType 参数的JdbcType类型
     * @throws SQLException 如果设置参数时发生SQL异常
     */
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter);
    }

    /**
     * 从ResultSet中获取可为空的结果。
     * @param rs ResultSet对象
     * @param columnName 列名称
     * @return 列中对应的字符串值
     * @throws SQLException 如果从ResultSet中获取数据时发生SQL异常
     */
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getString(columnName);
    }

    /**
     * 从ResultSet中获取可为空的结果。
     * @param rs ResultSet对象
     * @param columnIndex 列索引
     * @return 列中对应的字符串值
     * @throws SQLException 如果从ResultSet中获取数据时发生SQL异常
     */
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return rs.getString(columnIndex);
    }

    /**
     * 从CallableStatement中获取可为空的结果。
     * @param cs CallableStatement对象
     * @param columnIndex 列索引
     * @return 列中对应的字符串值
     * @throws SQLException 如果从CallableStatement中获取数据时发生SQL异常
     */
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return cs.getString(columnIndex);
    }
}

在源码里加了注释,写的非常清楚了,实际上我们只触碰到了MyBatis通过typeHandler将javaType和jdbcType相互转换,只这一步是不够的的,还需要将typeHandler注册到MyBatis的机制中去,在MyBatis中,采用注册机org.apache.ibatis.type.TypeHandlerRegistry的register方法进行注册,代码片段如下

/**
 * 类型处理器注册表,用于注册和管理各种类型的数据处理器(TypeHandler)。
 */
public final class TypeHandlerRegistry {
    // JDBC类型到类型处理器的映射
    private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap;
    // Java 类型到 JDBC 类型再到类型处理器的映射
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap;
    // 未知类型的标准类型处理器
    private final TypeHandler<Object> unknownTypeHandler;
    // 所有类型处理器的映射,包括泛型
    private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap;
    // 空的类型处理器映射,用于NULL类型处理
    private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
    // 默认枚举类型处理器
    private Class<? extends TypeHandler> defaultEnumTypeHandler;

    /**
     * 默认构造函数,使用默认配置初始化类型处理器注册表。
     */
    public TypeHandlerRegistry() {
        this(new Configuration());
    }

    /**
     * 构造函数,使用提供的配置初始化类型处理器注册表。
     *
     * @param configuration 配置对象,用于初始化类型处理器注册表。
     */
    public TypeHandlerRegistry(Configuration configuration) {
        this.jdbcTypeHandlerMap = new EnumMap(JdbcType.class);
        this.typeHandlerMap = new ConcurrentHashMap();
        this.allTypeHandlersMap = new HashMap();
        this.defaultEnumTypeHandler = EnumTypeHandler.class;
        this.unknownTypeHandler = new UnknownTypeHandler(configuration);

        // 注册基本Java类型和对应的类型处理器
        this.register((Class)Boolean.class, (TypeHandler)(new BooleanTypeHandler()));
        this.register((Class)Boolean.TYPE, (TypeHandler)(new BooleanTypeHandler()));
        // ...其他类型注册省略...
        // 注册特殊Java类型和对应的类型处理器,如字符串、日期等
        this.register((Class)String.class, (TypeHandler)(new StringTypeHandler()));
        this.register((Class)String.class, JdbcType.CHAR, (TypeHandler)(new StringTypeHandler()));
        // ...其他类型注册省略...
        // 注册JDBC类型和对应的类型处理器
        this.register((JdbcType)JdbcType.BOOLEAN, (TypeHandler)(new BooleanTypeHandler()));
        // ...其他类型注册省略...
    }

从这个构造方法中就能看的很清楚了,这样就实现了用代码形式注册typeHandler,而注册机TypeHandlerRegistry是Configuration配置类的一个属性,SessionFactory是通过Configuration配置类来构建的,大部分情况下都不会使用代码注册typeHandler,而是通过配置或者扫描注册,MyBatis自身就可以搞定,但是有时候规则不够用,比如使用枚举的时候,有特殊的转化规则,就需要自定义typeHandler处理

从MyBatis本身实现typeHandler的方式可以看出,要自定义typeHandler,就需要实现TypeHandler接口或者继承BaseTypeHandler,实际上BaseTypeHandler也实现了TypeHandler接口,完全可以仿照StringTypeHandler实现一个自定义的typeHandler

自定义typeHandler及配置

/**
 * 自定义类型处理器MyTypeHandler,实现对String类型的数据进行处理。
 */
public class MyTypeHandler implements TypeHandler<String> {
    // 日志记录器
    Logger logger = Logger.getLogger(MyTypeHandler.class);
    
    /**
     * 设置PreparedStatement参数。
     * 
     * @param ps PreparedStatement对象。
     * @param i 参数索引。
     * @param parameter 需要设置的参数值。
     * @param jdbcType 参数的JdbcType类型。
     * @throws SQLException 如果设置参数时发生SQL异常。
     */
    public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)throws SQLException {
        logger.info("设置string参数【"+parameter+"】");
        ps.setString(i, parameter);
    }
    
    /**
     * 从ResultSet中获取String类型的结果。
     * 
     * @param rs ResultSet对象。
     * @param columnName 列名称。
     * @return 返回获取到的String类型结果。
     * @throws SQLException 如果从ResultSet中获取数据时发生SQL异常。
     */
    public String getResult(ResultSet rs, String columnName)throws SQLException {
        String result = rs.getString(columnName);
        logger.info("获取string参数1【"+result+"】");
        return result;
    }
    
    /**
     * 从ResultSet中获取String类型的結果,通过列索引。
     * 
     * @param rs ResultSet对象。
     * @param columnIndex 列索引。
     * @return 返回获取到的String类型结果。
     * @throws SQLException 如果从ResultSet中获取数据时发生SQL异常。
     */
    public String getResult(ResultSet rs, int columnIndex)throws SQLException {
        String result = rs.getString(columnIndex);
        logger.info("获取string参数2【"+result+"】");
        return result;
    }
    
    /**
     * 从CallableStatement中获取String类型的結果,通过列索引。
     * 
     * @param cs CallableStatement对象。
     * @param columnIndex 列索引。
     * @return 返回获取到的String类型结果。
     * @throws SQLException 如果从CallableStatement中获取数据时发生SQL异常。
     */
    public String getResult(CallableStatement cs, int columnIndex)throws SQLException {
        String result = cs.getString(columnIndex);
        logger.info("获取string参数3【"+result+"】");
        return result;
    }
}

定义的typeHandler泛型为String,通过它把数据库的数据类型转化为String型,然后实现设置参数和获取结果集的方法,通过配置启动这个自定义的typeHandler

    <!-- 类型处理器 -->
    <typeHandlers>
        <typeHandler jdbcType="VARCHAR" javaType="String" handler="com.ssm.Utils.MyTypeHandler"/>
    </typeHandlers>

如此配置完成后,MyBatis就会将自定义的MyTypeHandler注册进来,当MyBatis做数据库类型和Java类型转换时,当jdbcType为VARCHAR,且javaType为String时,会使用MyTypeHandler进行转换

还可以显式启用typeHandler,如下在Mapper中直接指定
在这里插入图片描述
在实际执行代码的时候,从日志中不难看出,自定义typeHandler已经启用了

"C:\Program Files\Java\jdk1.8.0_311\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2023.2.1\lib\idea_rt.jar=56077:C:\Program Files\JetBrains\IntelliJ IDEA 2023.2.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_311\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_311\jre\lib\rt.jar;E:\Programs\Java\ChapterMybatis\target\classes;E:\maven392\jar\org\mybatis\mybatis\3.5.9\mybatis-3.5.9.jar;E:\maven392\jar\org\javassist\javassist\3.24.1-GA\javassist-3.24.1-GA.jar;E:\maven392\jar\cglib\cglib\3.2.10\cglib-3.2.10.jar;E:\maven392\jar\org\apache\ant\ant\1.10.3\ant-1.10.3.jar;E:\maven392\jar\org\apache\ant\ant-launcher\1.10.3\ant-launcher-1.10.3.jar;E:\maven392\jar\org\ow2\asm\asm\7.0\asm-7.0.jar;E:\maven392\jar\org\slf4j\slf4j-api\1.7.26\slf4j-api-1.7.26.jar;E:\maven392\jar\org\slf4j\slf4j-log4j12\1.7.26\slf4j-log4j12-1.7.26.jar;E:\maven392\jar\log4j\log4j\1.2.17\log4j-1.2.17.jar;E:\maven392\jar\org\apache\logging\log4j\log4j-core\2.11.2\log4j-core-2.11.2.jar;E:\maven392\jar\org\apache\logging\log4j\log4j-api\2.11.2\log4j-api-2.11.2.jar;E:\maven392\jar\com\mysql\mysql-connector-j\8.0.33\mysql-connector-j-8.0.33.jar;E:\maven392\jar\com\google\protobuf\protobuf-java\3.21.9\protobuf-java-3.21.9.jar" com.ssm.Main
DEBUG 2024-04-11 19:37:04,312 org.apache.ibatis.logging.LogFactory: Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
DEBUG 2024-04-11 19:37:04,348 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2024-04-11 19:37:04,348 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2024-04-11 19:37:04,348 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2024-04-11 19:37:04,348 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2024-04-11 19:37:04,541 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Opening JDBC Connection
DEBUG 2024-04-11 19:37:06,651 org.apache.ibatis.datasource.pooled.PooledDataSource: Created connection 837108062.
DEBUG 2024-04-11 19:37:06,651 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@31e5415e]
DEBUG 2024-04-11 19:37:06,655 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: select id, roleName, note from t_role where roleName like concat ('%',?,'%')
DEBUG 2024-04-11 19:37:06,688 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: zhang(String)
 INFO 2024-04-11 19:37:06,735 com.ssm.Utils.MyTypeHandler: 获取string参数1【123】
 INFO 2024-04-11 19:37:06,735 com.ssm.Utils.MyTypeHandler: 获取string参数1【123】
 INFO 2024-04-11 19:37:06,736 com.ssm.Utils.MyTypeHandler: 获取string参数1【123】
DEBUG 2024-04-11 19:37:06,737 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==      Total: 3
 INFO 2024-04-11 19:37:06,739 com.ssm.Main: [Role{id=2, roleName='zhang', note='123'}, Role{id=3, roleName='zhang', note='123'}, Role{id=4, roleName='zhang', note='123'}]
DEBUG 2024-04-11 19:37:06,740 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: SELECT id, roleName, note FROM t_role WHERE id = ?
DEBUG 2024-04-11 19:37:06,741 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 2(Integer)
 INFO 2024-04-11 19:37:06,744 com.ssm.Utils.MyTypeHandler: 获取string参数1【123】
DEBUG 2024-04-11 19:37:06,744 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==      Total: 1
 INFO 2024-04-11 19:37:06,744 com.ssm.Main: Role{id=2, roleName='zhang', note='123'}
DEBUG 2024-04-11 19:37:06,745 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: INSERT INTO t_role(roleName, note) VALUES (?, ?)
DEBUG 2024-04-11 19:37:06,746 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: zhang(String), 123(String)
DEBUG 2024-04-11 19:37:06,751 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-04-11 19:37:06,751 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Rolling back JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@31e5415e]
DEBUG 2024-04-11 19:37:06,752 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@31e5415e]
DEBUG 2024-04-11 19:37:06,753 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@31e5415e]
DEBUG 2024-04-11 19:37:06,753 org.apache.ibatis.datasource.pooled.PooledDataSource: Returned connection 837108062 to pool.

Process finished with exit code 0

然而有些时候枚举类型很多,系统需要自定义的typeHandler也很多,如果逐一指定会很麻烦,这种情况下可以通过扫描的方式配置typeHandler,直接将package配置进去

    <!-- 类型处理器 -->
    <typeHandlers>
        <package name="com.ssm.Utils"/>
    </typeHandlers>

但这样就没办法指定jabcTypejavaType了,需要通过注解处理他们,如下代码所示

package com.ssm.Utils;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;
import org.apache.log4j.Logger;

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

/**
 * 自定义类型处理器MyTypeHandler,实现对String类型的数据进行处理。
 */
@MappedTypes(String.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class MyTypeHandler implements TypeHandler<String> {
    // 日志记录器
    Logger logger = Logger.getLogger(MyTypeHandler.class);

    /**
     * 设置PreparedStatement参数。
     *
     * @param ps PreparedStatement对象。
     * @param i 参数索引。
     * @param parameter 需要设置的参数值。
     * @param jdbcType 参数的JdbcType类型。
     * @throws SQLException 如果设置参数时发生SQL异常。
     */
    public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)throws SQLException {
        logger.info("设置string参数【"+parameter+"】");
        ps.setString(i, parameter);
    }

    /**
     * 从ResultSet中获取String类型的结果。
     *
     * @param rs ResultSet对象。
     * @param columnName 列名称。
     * @return 返回获取到的String类型结果。
     * @throws SQLException 如果从ResultSet中获取数据时发生SQL异常。
     */
    public String getResult(ResultSet rs, String columnName)throws SQLException {
        String result = rs.getString(columnName);
        logger.info("获取string参数1【"+result+"】");
        return result;
    }

    /**
     * 从ResultSet中获取String类型的結果,通过列索引。
     *
     * @param rs ResultSet对象。
     * @param columnIndex 列索引。
     * @return 返回获取到的String类型结果。
     * @throws SQLException 如果从ResultSet中获取数据时发生SQL异常。
     */
    public String getResult(ResultSet rs, int columnIndex)throws SQLException {
        String result = rs.getString(columnIndex);
        logger.info("获取string参数2【"+result+"】");
        return result;
    }

    /**
     * 从CallableStatement中获取String类型的結果,通过列索引。
     *
     * @param cs CallableStatement对象。
     * @param columnIndex 列索引。
     * @return 返回获取到的String类型结果。
     * @throws SQLException 如果从CallableStatement中获取数据时发生SQL异常。
     */
    public String getResult(CallableStatement cs, int columnIndex)throws SQLException {
        String result = cs.getString(columnIndex);
        logger.info("获取string参数3【"+result+"】");
        return result;
    }
}

在代码中注解@MappedTypes声明javaType,注解@MappedJdbcTypes则声明jdbcType,这样就指定了MyTypeHandler的数据转换类型

枚举typeHandler

在绝大多数情况下,需要自定义typeHandler的原因是使用了枚举,在MyBatis中已经定义了两个类作为枚举类型的支持,这两个类分别是EnumOrdinalTypeHandlerEnumTypeHandler,但这两个实用性不强

首先定义一个简单的枚举类

package com.ssm.pojo;

/**
 * 性别枚举类,用于定义性别及其对应的代码
 */
public enum SexEnum {
    // 男性枚举实例,性别代码为1
    MALE(1, "男"),
    // 女性枚举实例,性别代码为0
    FEMALE(0, "女");

    // 性别代码
    private  int seid;
    // 性别名称
    private String name;

    /**
     * 构造方法,用于初始化性别枚举实例
     * @param seid 性别代码
     * @param name 性别名称
     */
    SexEnum(int seid, String name) {
        this.seid = seid;
        this.name = name;
    }

    /**
     * 获取性别代码
     * @return 性别代码
     */
    public int getId() {
        return seid;
    }

    /**
     * 设置性别代码的方法,当前为空实现,根据需要进行扩展
     * @param seid 性别代码
     */
    public void setId(int seid) {
    }
    /**
     * 获取性别名称
     * @return 性别名称
     */
    public String getName() {
        return name;
    }

    /**
     * 设置性别名称的方法,当前为空实现,根据需要进行扩展
     * @param name 性别名称
     */
    public void setName(String name) {
    }
}

对应数据库表定义POJO

package com.ssm.pojo;

public class User {
    private int userid; // 用户id
    private String username; // 用户名
    private String password; // 密码
    private SexEnum sex; // 性别
    private String phone; // 手机号码
    private String phonenumber; // 电话号码
    private String email; // 电子邮箱
    private String comment; // 备注信息

    /**
     * 获取用户ID
     * @return 用户ID
     */
    public int getId() {
        return userid;
    }

    /**
     * 设置用户ID
     * @param id 用户ID
     */
    public void setId(int id) {
        this.userid = id;
    }

    /**
     * 获取用户名
     * @return 用户名
     */
    public String getUserName() {
        return username;
    }

    /**
     * 设置用户名
     * @param userName 用户名
     */
    public void setUserName(String userName) {
        this.username = userName;
    }

    /**
     * 获取密码
     * @return 密码
     */
    public String getPassword() {
        return password;
    }

    /**
     * 设置密码
     * @param password 密码
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * 获取性别
     * @return 性别枚举
     */
    public SexEnum getSex() {
        return sex;
    }

    /**
     * 设置性别
     * @param sex 性别枚举
     */
    public void setSex(SexEnum sex) {
        this.sex = sex;
    }

    /**
     * 获取手机号码
     * @return 手机号码
     */
    public String getMobile() {
        return phone;
    }

    /**
     * 设置手机号码
     * @param phone 手机号码
     */
    public void setMobile(String phone) {
        this.phone = phone;
    }

    /**
     * 获取电话号码
     * @return 电话号码
     */
    public String getTel() {
        return phonenumber;
    }

    /**
     * 设置电话号码
     * @param phonenumber 电话号码
     */
    public void setTel(String phonenumber) {
        this.phonenumber = phonenumber;
    }

    /**
     * 获取电子邮箱
     * @return 电子邮箱
     */
    public String getEmail() {
        return email;
    }

    /**
     * 设置电子邮箱
     * @param email 电子邮箱
     */
    public void setEmail(String email) {
        this.email = email;
    }

    /**
     * 获取备注信息
     * @return 备注信息
     */
    public String getComment() {
        return comment;
    }

    /**
     * 设置备注信息
     * @param comment 备注信息
     */
    public void setComment(String comment) {
        this.comment = comment;
    }

    @Override
    public String toString() {
        return "User [userid=" + userid + ", username=" + username + ", password=" + password + ", sex=" + sex
                + ", phone=" + phone + ", phonenumber=" + phonenumber + ", email=" + email + ", comment=" + comment
                + "]";
    }

}

然后定义Mapper接口

package com.ssm.Dao;

import com.ssm.pojo.User;

public interface UserDao {
    public User getUser(Integer id);
}

然后定义映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- 定义mapper的命名空间 -->
<mapper namespace="com.ssm.Dao.UserDao">
    <!-- 定义结果映射,用于将数据库中的数据映射成Role对象 -->
    <resultMap id="userMap" type="com.ssm.pojo.User">
        <id property="userid" column="userid"/> <!-- 主键映射 -->
        <result property="username" column="username"/> <!-- 角色名映射 -->
        <result property="password" column="password"/> <!-- 备注映射 -->
        <result property="sex" column="sex" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
        <result property="phone" column="phone"/>
        <result property="email" column="email"/>
        <result property="phonenumber" column="phonenumber"/>
        <result property="comment" column="comment"/>
    </resultMap>
    <select id="getUser" parameterType="int" resultMap="userMap">
        SELECT userid, username, password, sex, phone, email, phonenumber,comment FROM user WHERE userid = #{id}
    </select>


</mapper>

执行结果如下
在这里插入图片描述
在这里插入图片描述
从执行结果中不难看出,EnumOrdinalTypeHandler是根据枚举的下标索引的方式匹配的,与枚举里设置的值无关,这个地方非常容易犯错,MyBatis自动映射并使用了转换类,它要求数据库返回一个整数作为其下标,然后根据下标找到对应的枚举类型

修改一下表的值
在这里插入图片描述

修改typeHandler
在这里插入图片描述
再次执行代码
在这里插入图片描述
对比一下之前定义的枚举
在这里插入图片描述
不难看出EnumTypeHandler是通过枚举名称转化为对应的枚举值的

自定义枚举typeHandler

package com.ssm.Utils;

import com.ssm.pojo.SexEnum;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;

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

/**
 * 自定义枚举类型处理器,用于将SexEnum类型与数据库中的INTEGER类型进行映射。
 */
@MappedTypes(SexEnum.class)
@MappedJdbcTypes(JdbcType.INTEGER)
public class SexEnumTypeHandler extends BaseTypeHandler<SexEnum> {
    
    /**
     * 设置非空参数,将枚举值映射为对应的整型ID,绑定到PreparedStatement中。
     * 
     * @param ps PreparedStatement,用于执行SQL语句的预编译语句对象。
     * @param i 参数在PreparedStatement中的索引。
     * @param parameter 要设置的参数值,此处为SexEnum枚举实例。
     * @param jdbcType 参数在数据库中对应的JdbcType类型。
     * @throws SQLException 如果设置参数时发生SQL异常。
     */
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, SexEnum parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter.getId());
    }
    
    /**
     * 从ResultSet中获取可空的结果值,将数据库中的整型ID映射为枚举值。
     * 
     * @param rs ResultSet,执行SQL查询后返回的结果集。
     * @param columnName 列名称,从结果集中获取值时使用的列名。
     * @return SexEnum 枚举实例,如果结果为NULL,则返回null。
     * @throws SQLException 如果从结果集中获取数据时发生SQL异常。
     */
    @Override
    public SexEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
        int id = rs.getInt(columnName);
        return SexEnum.getSexById(id);
    }
    
    /**
     * 从ResultSet中获取可空的结果值,将数据库中的整型ID映射为枚举值。
     * 
     * @param rs ResultSet,执行SQL查询后返回的结果集。
     * @param columnIndex 列索引,从结果集中获取值时使用的列索引。
     * @return SexEnum 枚举实例,如果结果为NULL,则返回null。
     * @throws SQLException 如果从结果集中获取数据时发生SQL异常。
     */
    @Override
    public SexEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int id = rs.getInt(columnIndex);
        return SexEnum.getSexById(id);
    }
    
    /**
     * 从CallableStatement中获取可空的结果值,将数据库中的整型ID映射为枚举值。
     * 
     * @param cs CallableStatement,用于执行存储过程或函数的语句对象。
     * @param columnIndex 列索引,从结果集中获取值时使用的列索引。
     * @return SexEnum 枚举实例,如果结果为NULL,则返回null。
     * @throws SQLException 如果从结果集中获取数据时发生SQL异常。
     */
    @Override
    public SexEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int id = cs.getInt(columnIndex);
        return SexEnum.getSexById(id);
    }
    
}

在枚举类定义中添加方法

    public static SexEnum getSexById(int seid) {
        // 遍历所有的性别枚举值
        for (SexEnum sex : SexEnum.values()) {
            // 如果找到与给定ID匹配的性别枚举,则返回该枚举
            if (sex.getId()==seid){
                return sex;
            }
        }
        // 如果没有找到匹配的性别枚举,返回null
        return null;
    }

将UserMapper.xml里的typeHandler配置去掉,在MyBatis的核心配置配置文件中用扫描的方式添加typeHandler

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- 定义mapper的命名空间 -->
<mapper namespace="com.ssm.Dao.UserDao">
    <!-- 定义结果映射,用于将数据库中的数据映射成Role对象 -->
    <resultMap id="userMap" type="com.ssm.pojo.User">
        <id property="userid" column="userid"/> <!-- 主键映射 -->
        <result property="username" column="username"/> <!-- 角色名映射 -->
        <result property="password" column="password"/> <!-- 备注映射 -->
        <result property="sex" column="sex"/>
        <result property="phone" column="phone"/>
        <result property="email" column="email"/>
        <result property="phonenumber" column="phonenumber"/>
        <result property="comment" column="comment"/>
    </resultMap>
    <select id="getUser" parameterType="int" resultMap="userMap">
        SELECT userid, username, password, sex, phone, email, phonenumber,comment FROM user WHERE userid = #{id}
    </select>


</mapper>

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTDConfig3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- 配置文件开始 -->
<configuration>
    <!-- 配置属性 -->
    <properties resource="config.properties"/>
    <!-- 类型别名 -->
    <typeAliases>
        <typeAlias alias="Role" type="com.ssm.pojo.Role"/>
    </typeAliases>
    <!-- 类型处理器 -->
    <typeHandlers>
        <package name="com.ssm.Utils"/>
    </typeHandlers>
    <!-- 数据库环境配置,默认环境为development -->
    <environments default="development">
        <environment id="development">
            <!-- 事务管理器配置 -->
            <transactionManager type="JDBC"/>
            <!-- 数据源配置,使用连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="${className}"/> <!-- 驱动 -->
                <property name="url" value="${url}"/> <!-- URL -->
                <property name="username" value="${username}"/> <!-- 用户名 -->
                <property name="password" value="${password}"/> <!-- 密码 -->
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/UserMapper.xml"/> <!-- XML映射文件路径 -->
    </mappers>
</configuration>
        <!-- 配置文件结束 -->

再次执行代码,便会使用自定义枚举typeHandler

mappers

    <!-- 有很多种方式定义mappers配置,用于指定MyBatis要使用的Mapper接口或XML映射文件 -->
    <mappers>
        <!-- 指定一个外部的XML映射文件,这里使用相对路径 -->
        <mapper resource="mapper/UserMapper.xml"/>
        <!-- 指定一个包下所有的Mapper接口,这里会扫描com.ssm.Dao包下所有的接口 -->
        <package name="com.ssm.Dao"/>
        <!-- 指定一个具体的Mapper接口,这里是com.ssm.Dao.RoleDao接口 -->
        <mapper class="com.ssm.Dao.RoleDao"/>
        <!-- 指定一个外部的XML映射文件的URL地址,这里使用绝对路径 -->
        <mapper url="file:///var/pappers/com/ssm/mapper/roleMapper.xml"/>
    </mappers>

其他配置

ObjectFactory

MyBatis会使用工厂ObjectFactory构建结果集实例,默认情况下MyBatis会使用其定义的工厂DefaultObjectFactory(org.apache.ibatis.reflection.factory.DefaultObjectFactory)完成工作,MyBatis也允许注册自定义的ObjectFactory,如果自定义则需要实现org.apache.ibatis.reflection.factory.ObjectFactory,并进行配置,通常不需要自定义返回规则,默认的大部分场景都够用

environment

MyBatis框架中的环境配置(environments)部分允许用户为应用程序的不同运行环境(如开发环境、测试环境、生产环境等)定义特定的数据库连接和事务管理策略

    <!-- 数据库环境配置,默认环境为development -->
    <environments default="development">
        <environment id="development">
            <!-- 事务管理器配置 -->
            <transactionManager type="JDBC"/>
            <!-- 数据源配置,使用连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="${className}"/> <!-- 驱动 -->
                <property name="url" value="${url}"/> <!-- URL -->
                <property name="username" value="${username}"/> <!-- 用户名 -->
                <property name="password" value="${password}"/> <!-- 密码 -->
            </dataSource>
        </environment>
        <environment id="test">
            <transactionManager type="MANAGED"></transactionManager>
            <dataSource type="UNPOOLED">
                <property name="driver" value="${className}"/> <!-- 驱动 -->
                <property name="url" value="${url}"/> <!-- URL -->
                <property name="username" value="${username}"/> <!-- 用户名 -->
                <property name="password" value="${password}"/> <!-- 密码 -->
            </dataSource>
        </environment>
    </environments>

environment可以配置多组数据源,然后通过default属性配置默认的数据源,只需要default值和某一组数据源配置的唯一值id匹配;没一个environment包含两个元素transactionManagerdataSource,其中transactionManager配置事务管理器,datasource则配置数据源链接信息

事务管理器(transactionManager)

transactionManager选项是用来指定事务管理器类型的,它决定了MyBatis如何处理事务的生命周期(即事务的开始、提交、回滚等操作)。在MyBatis中,主要有以下两种内置的事务管理器类型:

  • JDBC:当transactionManager设置为JDBC时,MyBatis将使用JdbcTransactionFactory来创建并管理事务。这意味着:

    • 事务控制:MyBatis会直接通过底层的JDBC Connection 对象来执行事务的开启、提交和回滚操作。具体来说,当执行SQL操作时,MyBatis会确保在一个事务上下文中进行,这个事务是由MyBatis自身负责开启、管理并最终结束的。
    • 依赖关系:此种方式依赖于数据源(dataSource)提供的连接。通常,数据源会使用连接池技术来管理数据库连接,确保高效的资源复用。在事务执行期间,MyBatis会从数据源获取一个连接,执行所有相关的SQL语句,然后在事务结束时根据业务逻辑(如是否有异常抛出)决定是否提交或回滚事务。
    • 适用场景:适用于那些不处于容器(如Java EE应用服务器)管理环境中的应用,或者用户希望直接利用JDBC API进行事务管理的情况。这种模式下,MyBatis自行处理事务的完整生命周期,无需依赖外部事务协调者。
  • MANAGED:当transactionManager设置为MANAGED时,MyBatis将使用ManagedTransactionFactory来作为事务管理器。在这种模式下:

    • 事务控制:MyBatis不对事务进行直接管理,而是假定存在一个外部的事务管理器(如Java EE应用服务器的容器事务服务,如JTA事务管理器)负责控制事务的边界。MyBatis仅参与当前存在的事务,不开启新的事务,也不负责提交或回滚事务。这意味着,事务的启动、提交、回滚等操作完全由外部容器来控制。
    • 依赖关系:在这种模式下,MyBatis与容器紧密集成,依赖于容器提供的事务管理服务。应用程序必须在同一个事务上下文中调用MyBatis的操作,事务的开始、结束以及同步点(savepoint)的设置等均由容器自动处理。
    • 适用场景:适用于运行在Java EE应用服务器或其他提供了事务管理服务的容器环境中的应用。例如,在Spring、EJB等环境下,可以通过容器提供的事务管理机制来统一管理事务,包括跨多个服务或资源的分布式事务。在这种情况下,MyBatis只需参与到已有的全局事务中,不需要也不应该单独控制事务。
  • 总结来说,transactionManager选项的选择取决于项目的实际部署环境和事务管理需求:

    • 如果应用独立运行,或者需要MyBatis直接控制事务,选择JDBC,MyBatis将通过JDBC API自行管理事务。
    • 如果应用运行在具有容器管理事务能力的环境中,如Java EE服务器,选择MANAGED,MyBatis将参与由容器协调的事务,避免了事务管理的双重处理,有利于保持事务边界的一致性。

在MyBatis中,事务管理器transactionManager提供了两个实现类,都实现了Transaction接口org.apache.ibatis.transaction.Transaction

/**
 * 事务接口,定义了对数据库事务的基本操作。
 */
package org.apache.ibatis.transaction;

import java.sql.Connection;
import java.sql.SQLException;

public interface Transaction {
    
    /**
     * 获取一个数据库连接。
     * 
     * @return Connection 数据库连接对象。
     * @throws SQLException 如果获取连接时发生错误,则抛出SQLException。
     */
    Connection getConnection() throws SQLException;

    /**
     * 提交当前事务。
     * 
     * @throws SQLException 如果提交事务时发生错误,则抛出SQLException。
     */
    void commit() throws SQLException;

    /**
     * 回滚当前事务。
     * 
     * @throws SQLException 如果回滚事务时发生错误,则抛出SQLException。
     */
    void rollback() throws SQLException;

    /**
     * 关闭当前事务。
     * 
     * @throws SQLException 如果关闭事务时发生错误,则抛出SQLException。
     */
    void close() throws SQLException;

    /**
     * 获取事务的超时时间。
     * 
     * @return Integer 事务的超时时间,以秒为单位。如果未设置超时时间,返回null。
     * @throws SQLException 如果获取超时时间时发生错误,则抛出SQLException。
     */
    Integer getTimeout() throws SQLException;
}

Transaction的两个实现类,分别是JdbcTransaction和ManagedTransaction

//
// JDBC事务管理器实现类,负责管理数据库连接的事务。
//
package org.apache.ibatis.transaction.jdbc;

import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionException;

/**
 * JDBC事务实现类,提供事务管理功能。
 */
public class JdbcTransaction implements Transaction {
    private static final Log log = LogFactory.getLog(JdbcTransaction.class);
    protected Connection connection; // 数据库连接
    protected DataSource dataSource; // 数据源
    protected TransactionIsolationLevel level; // 事务隔离级别
    protected boolean autoCommit; // 是否自动提交

    /**
     * 基于数据源和事务配置构造事务对象。
     *
     * @param ds            数据源
     * @param desiredLevel  期望的事务隔离级别
     * @param desiredAutoCommit  期望的自动提交状态
     */
    public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
        this.dataSource = ds;
        this.level = desiredLevel;
        this.autoCommit = desiredAutoCommit;
    }

    /**
     * 基于数据库连接构造事务对象。
     *
     * @param connection  数据库连接
     */
    public JdbcTransaction(Connection connection) {
        this.connection = connection;
    }

    /**
     * 获取数据库连接。如果连接为空,则尝试从数据源打开一个新的连接。
     *
     * @return 数据库连接
     * @throws SQLException 如果获取连接失败
     */
    public Connection getConnection() throws SQLException {
        if (this.connection == null) {
            this.openConnection();
        }

        return this.connection;
    }

    /**
     * 提交事务。
     *
     * @throws SQLException 如果提交失败
     */
    public void commit() throws SQLException {
        if (this.connection != null && !this.connection.getAutoCommit()) {
            if (log.isDebugEnabled()) {
                log.debug("Committing JDBC Connection [" + this.connection + "]");
            }

            this.connection.commit();
        }

    }

    /**
     * 回滚事务。
     *
     * @throws SQLException 如果回滚失败
     */
    public void rollback() throws SQLException {
        if (this.connection != null && !this.connection.getAutoCommit()) {
            if (log.isDebugEnabled()) {
                log.debug("Rolling back JDBC Connection [" + this.connection + "]");
            }

            this.connection.rollback();
        }

    }

    /**
     * 关闭事务,重置自动提交状态并关闭数据库连接。
     *
     * @throws SQLException 如果关闭连接失败
     */
    public void close() throws SQLException {
        if (this.connection != null) {
            this.resetAutoCommit();
            if (log.isDebugEnabled()) {
                log.debug("Closing JDBC Connection [" + this.connection + "]");
            }

            this.connection.close();
        }

    }

    /**
     * 设置数据库连接的自动提交状态为期望值。
     *
     * @param desiredAutoCommit 期望的自动提交状态
     */
    protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
        try {
            if (this.connection.getAutoCommit() != desiredAutoCommit) {
                if (log.isDebugEnabled()) {
                    log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + this.connection + "]");
                }

                this.connection.setAutoCommit(desiredAutoCommit);
            }

        } catch (SQLException var3) {
            throw new TransactionException("Error configuring AutoCommit.  Your driver may not support getAutoCommit() or setAutoCommit(). Requested setting: " + desiredAutoCommit + ".  Cause: " + var3, var3);
        }
    }

    /**
     * 重置数据库连接的自动提交状态为true。
     */
    protected void resetAutoCommit() {
        try {
            if (!this.connection.getAutoCommit()) {
                if (log.isDebugEnabled()) {
                    log.debug("Resetting autocommit to true on JDBC Connection [" + this.connection + "]");
                }

                this.connection.setAutoCommit(true);
            }
        } catch (SQLException var2) {
            if (log.isDebugEnabled()) {
                log.debug("Error resetting autocommit to true before closing the connection.  Cause: " + var2);
            }
        }

    }

    /**
     * 从数据源打开一个新的数据库连接,并设置事务隔离级别和自动提交状态。
     *
     * @throws SQLException 如果打开连接或设置失败
     */
    protected void openConnection() throws SQLException {
        if (log.isDebugEnabled()) {
            log.debug("Opening JDBC Connection");
        }

        this.connection = this.dataSource.getConnection();
        if (this.level != null) {
            this.connection.setTransactionIsolation(this.level.getLevel());
        }

        this.setDesiredAutoCommit(this.autoCommit);
    }

    /**
     * 获取事务超时时间。当前实现始终返回null。
     *
     * @return null,表示没有超时时间
     * @throws SQLException 如果获取失败
     */
    public Integer getTimeout() throws SQLException {
        return null;
    }
}


package org.apache.ibatis.transaction.managed;

import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;

/**
 * 管理型事务实现类,用于在容器管理的事务环境中使用。
 */
public class ManagedTransaction implements Transaction {
    private static final Log log = LogFactory.getLog(ManagedTransaction.class);
    private DataSource dataSource; // 数据源
    private TransactionIsolationLevel level; // 事务隔离级别
    private Connection connection; // JDBC连接
    private final boolean closeConnection; // 是否关闭连接标志

    /**
     * 构造函数,传入已有的数据库连接。
     *
     * @param connection 数据库连接
     * @param closeConnection 连接是否由本事务关闭
     */
    public ManagedTransaction(Connection connection, boolean closeConnection) {
        this.connection = connection;
        this.closeConnection = closeConnection;
    }

    /**
     * 构造函数,传入数据源和事务隔离级别。
     *
     * @param ds 数据源
     * @param level 事务隔离级别
     * @param closeConnection 连接是否由本事务关闭
     */
    public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) {
        this.dataSource = ds;
        this.level = level;
        this.closeConnection = closeConnection;
    }

    /**
     * 获取数据库连接。如果连接未打开,则打开一个新连接。
     *
     * @return JDBC连接
     * @throws SQLException 如果获取连接失败
     */
    public Connection getConnection() throws SQLException {
        if (this.connection == null) {
            this.openConnection();
        }

        return this.connection;
    }

    /**
     * 提交事务。此实现为空,因为事务提交由容器管理。
     *
     * @throws SQLException 如果操作失败
     */
    public void commit() throws SQLException {
    }

    /**
     * 回滚事务。此实现为空,因为事务回滚由容器管理。
     *
     * @throws SQLException 如果操作失败
     */
    public void rollback() throws SQLException {
    }

    /**
     * 关闭事务。如果标志为关闭连接,并且连接不为空,则关闭连接。
     *
     * @throws SQLException 如果关闭连接失败
     */
    public void close() throws SQLException {
        if (this.closeConnection && this.connection != null) {
            if (log.isDebugEnabled()) {
                log.debug("Closing JDBC Connection [" + this.connection + "]");
            }

            this.connection.close();
        }

    }

    /**
     * 打开数据库连接。如果日志调试模式开启,打印日志信息。
     *
     * @throws SQLException 如果打开连接失败
     */
    protected void openConnection() throws SQLException {
        if (log.isDebugEnabled()) {
            log.debug("Opening JDBC Connection");
        }

        this.connection = this.dataSource.getConnection();
        if (this.level != null) {
            this.connection.setTransactionIsolation(this.level.getLevel());
        }

    }

    /**
     * 获取事务超时时间。此实现始终返回null,因为超时由容器管理。
     *
     * @return null,表示没有超时设置
     * @throws SQLException 如果操作失败
     */
    public Integer getTimeout() throws SQLException {
        return null;
    }
}

他们分别对应着JdbcTransactionFactory和ManagedTransactionFactory两个工厂类,这两个工厂类是TransactionFactory接口的两个实现类,通过它们生成对应的Transaction对象,如果不想使用采用MyBatis的规则,也可以自定义事务管理器,和源码一样同样需要实现Transaction和TransactionFactory接口

package com.ssm.Utils;

import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.util.Properties;

/**
 * 自定义事务工厂类,实现了TransactionFactory接口。
 * 用于创建MyTransaction事务对象。
 */
public class MyTransactionFactory implements TransactionFactory {

    /**
     * 设置属性方法。
     * 该方法目前没有实现具体功能,留作扩展使用。
     *
     * @param properties 事务工厂的属性配置
     */
    public void setProperties(Properties properties){

    }

    /**
     * 基于已有的数据库连接创建一个新的事务对象。
     *
     * @param conn 已经存在的数据库连接
     * @return 返回一个MyTransaction实例,代表一个新的事务
     */
    public Transaction newTransaction(Connection conn){
        return new MyTransaction(conn);
    }

    /**
     * 基于数据源和事务隔离级别创建一个新的事务对象。
     *
     * @param dataSource 数据源,用于获取数据库连接
     * @param level 事务的隔离级别
     * @param autoCommit 是否自动提交事务
     * @return 返回一个MyTransaction实例,代表一个新的事务
     */
    public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit){
        return new MyTransaction(dataSource,level,autoCommit);
    }

}

/**
 * 自定义事务类,继承自JdbcTransaction,实现Transaction接口。
 * 提供了对数据源或连接进行事务管理的能力。
 */
package com.ssm.Utils;

import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.jdbc.JdbcTransaction;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class MyTransaction extends JdbcTransaction implements Transaction {

    /**
     * 构造函数,基于数据源和事务隔离级别创建事务。
     * 
     * @param datasource 数据源,用于获取数据库连接。
     * @param desiredLevel 期望的事务隔离级别。
     * @param desiredAutoCommit 期望的自动提交状态。
     */
    public MyTransaction(DataSource datasource, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit){
        super(datasource,desiredLevel,desiredAutoCommit);
    }

    /**
     * 构造函数,基于已有的数据库连接创建事务。
     * 
     * @param connection 已经存在的数据库连接。
     */
    public MyTransaction(Connection connection){
        super(connection);
    }

    /**
     * 获取当前事务的数据库连接。
     * 
     * @return 返回事务关联的数据库连接。
     * @throws SQLException 如果获取连接失败则抛出SQLException。
     */
    @Override
    public Connection getConnection() throws SQLException{
        return super.getConnection();
    }

    /**
     * 提交当前事务。
     * 
     * @throws SQLException 如果提交事务失败则抛出SQLException。
     */
    @Override
    public void commit() throws SQLException{
        super.commit();
    }

    /**
     * 回滚当前事务。
     * 
     * @throws SQLException 如果回滚事务失败则抛出SQLException。
     */
    @Override
    public void rollback() throws SQLException{
        super.rollback();
    }

    /**
     * 关闭当前事务,释放资源。
     * 
     * @throws SQLException 如果关闭事务失败则抛出SQLException。
     */
    @Override
    public void close() throws SQLException{
        super.close();
    }

    /**
     * 获取当前事务的超时时间。
     * 
     * @return 返回事务的超时时间(秒)。
     * @throws SQLException 如果获取超时时间失败则抛出SQLException。
     */
    @Override
    public Integer getTimeout() throws SQLException{
        return super.getTimeout();
    }
}

然后配置到事务管理器中即可

  <!-- 事务管理器配置 -->
  <transactionManager type="com.ssm.Utils.MyTransactionFactory"/>
数据源环境(datasource)

MyBatis提供了3个工厂类用于根据配置构建数据源对象,PooledDataSourceFactoryUnPooledDataSourceFactoryJndiDataSourceFactory它们都是DataSourceFactory的实现类,并且PooledDataSourceFactory又继承了UnPooledDataSourceFactory

/**
 * 数据源工厂接口,用于创建和配置数据源。
 */
package org.apache.ibatis.datasource;

import java.util.Properties; // 导入Properties类,用于配置属性
import javax.sql.DataSource; // 导入DataSource接口,代表一个数据库连接池

public interface DataSourceFactory {
    /**
     * 设置属性配置。
     * @param var1 Properties对象,包含需要设置的属性配置。
     */
    void setProperties(Properties var1);

    /**
     * 获取数据源实例。
     * @return DataSource数据源实例,实现了javax.sql.DataSource接口。
     */
    DataSource getDataSource();
}

/**
 * 基于Apache MyBatis的不池化数据源工厂类的扩展,提供一个池化数据源工厂。
 * 这个类继承自UnpooledDataSourceFactory,是为了利用池化机制管理数据源,
 * 从而提高数据库连接的效率和利用率。
 */
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;

public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
    /**
     * 构造函数,初始化数据源为一个池化数据源实例。
     * 这个构造函数通过创建一个PooledDataSource实例并将其赋值给dataSource属性,
     * 为数据源池的使用提供了基础。
     */
    public PooledDataSourceFactory() {
        this.dataSource = new PooledDataSource();
    }
}


//
// 该源代码从一个.class文件由IntelliJ IDEA重构而来
// (由FernFlower反编译器提供支持)
//

package org.apache.ibatis.datasource.unpooled;

import java.util.Iterator;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.datasource.DataSourceException;
import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

/**
 * 未池化的数据源工厂类,实现了DataSourceFactory接口。
 * 用于创建不使用池化的数据源实例。
 */
public class UnpooledDataSourceFactory implements DataSourceFactory {
    private static final String DRIVER_PROPERTY_PREFIX = "driver."; // 驱动属性前缀
    private static final int DRIVER_PROPERTY_PREFIX_LENGTH = "driver.".length(); // 驱动属性前缀长度
    protected DataSource dataSource = new UnpooledDataSource(); // 未池化的数据源实例

    /**
     * 默认构造函数
     */
    public UnpooledDataSourceFactory() {
    }

    /**
     * 设置数据源属性。
     * @param properties 数据源的属性,可以包含驱动和其他数据源相关的配置。
     */
    public void setProperties(Properties properties) {
        Properties driverProperties = new Properties(); // 用于存储驱动特定的属性
        MetaObject metaDataSource = SystemMetaObject.forObject(this.dataSource);
        Iterator var4 = properties.keySet().iterator();

        while(var4.hasNext()) {
            Object key = var4.next();
            String propertyName = (String)key;
            String value;
            if (propertyName.startsWith("driver.")) { // 处理驱动特定的属性
                value = properties.getProperty(propertyName);
                driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
            } else { // 处理其他数据源属性
                if (!metaDataSource.hasSetter(propertyName)) {
                    throw new DataSourceException("Unknown DataSource property: " + propertyName);
                }

                value = (String)properties.get(propertyName);
                Object convertedValue = this.convertValue(metaDataSource, propertyName, value); // 转换属性值的类型
                metaDataSource.setValue(propertyName, convertedValue);
            }
        }

        if (driverProperties.size() > 0) {
            metaDataSource.setValue("driverProperties", driverProperties); // 设置驱动属性
        }

    }

    /**
     * 获取数据源实例。
     * @return DataSource 数据源实例。
     */
    public DataSource getDataSource() {
        return this.dataSource;
    }

    /**
     * 类型转换方法,将字符串类型的属性值转换为对应属性的类型。
     * @param metaDataSource 元对象,用于获取属性类型。
     * @param propertyName 属性名。
     * @param value 属性值。
     * @return 转换后的值。
     */
    private Object convertValue(MetaObject metaDataSource, String propertyName, String value) {
        Object convertedValue = value; // 默认情况下,不转换类型
        Class<?> targetType = metaDataSource.getSetterType(propertyName); // 获取属性的类型
        if (targetType != Integer.class && targetType != Integer.TYPE) { // 判断不是整型
            if (targetType != Long.class && targetType != Long.TYPE) { // 判断不是长整型
                if (targetType == Boolean.class || targetType == Boolean.TYPE) { // 判断是布尔型
                    convertedValue = Boolean.valueOf(value);
                }
            } else {
                convertedValue = Long.valueOf(value);
            }
        } else {
            convertedValue = Integer.valueOf(value);
        }

        return convertedValue;
    }
}

package org.apache.ibatis.datasource.jndi;

import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.apache.ibatis.datasource.DataSourceException;
import org.apache.ibatis.datasource.DataSourceFactory;

/**
 * JNDI数据源工厂类,用于通过JNDI查找获取数据源。
 */
public class JndiDataSourceFactory implements DataSourceFactory {
    public static final String INITIAL_CONTEXT = "initial_context"; // 初始化上下文属性名
    public static final String DATA_SOURCE = "data_source"; // 数据源属性名
    public static final String ENV_PREFIX = "env."; // 环境属性前缀
    private DataSource dataSource; // 数据源

    /**
     * 构造函数,初始化数据源。
     */
    public JndiDataSourceFactory() {
    }

    /**
     * 设置属性,通过这些属性来查找和配置数据源。
     *
     * @param properties 包含配置数据源所需信息的属性集合。
     */
    public void setProperties(Properties properties) {
        try {
            Properties env = getEnvProperties(properties); // 获取环境属性
            InitialContext initCtx;
            if (env == null) {
                initCtx = new InitialContext(); // 无环境属性时创建初始上下文
            } else {
                initCtx = new InitialContext(env); // 使用环境属性创建初始上下文
            }

            // 查找并配置数据源
            if (properties.containsKey("initial_context") && properties.containsKey("data_source")) {
                Context ctx = (Context) initCtx.lookup(properties.getProperty("initial_context"));
                this.dataSource = (DataSource) ctx.lookup(properties.getProperty("data_source"));
            } else if (properties.containsKey("data_source")) {
                this.dataSource = (DataSource) initCtx.lookup(properties.getProperty("data_source"));
            }

        } catch (NamingException var5) {
            // 查找数据源失败时抛出异常
            throw new DataSourceException("There was an error configuring JndiDataSourceTransactionPool. Cause: " + var5, var5);
        }
    }

    /**
     * 获取数据源。
     *
     * @return 配置好的数据源实例。
     */
    public DataSource getDataSource() {
        return this.dataSource;
    }

    /**
     * 从所有属性中提取环境属性。
     *
     * @param allProps 包含所有属性的集合。
     * @return 环境属性的集合,如果没有则返回null。
     */
    private static Properties getEnvProperties(Properties allProps) {
        String PREFIX = "env."; // 环境属性前缀
        Properties contextProperties = null;
        Iterator var3 = allProps.entrySet().iterator();

        while (var3.hasNext()) {
            Map.Entry<Object, Object> entry = (Map.Entry) var3.next();
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();
            // 提取环境属性
            if (key.startsWith("env.")) {
                if (contextProperties == null) {
                    contextProperties = new Properties();
                }

                contextProperties.put(key.substring("env.".length()), value);
            }
        }

        return contextProperties;
    }
}

对应的配置选项

<dataSource type="POOLED">
<dataSource type="UNPOOLED">
<dataSource type="JNDI">
  • UNPOOLED: 这是最简单且轻量级的数据源类型。每次执行SQL查询时,MyBatis都会创建一个新的数据库连接,使用完后立即关闭。这种方式适用于数据库连接需求极少且短暂的应用场景,因为它没有额外的连接池管理开销。然而,频繁创建和销毁连接可能导致性能下降和资源浪费。
  • POOLED: 这是最常用的数据源类型,它利用第三方连接池实现(如HikariCP、C3P0、DBCP等),预先创建并维护一定数量的数据库连接,供应用程序复用。当应用程序需要访问数据库时,可以从连接池中获取已存在的连接,使用完毕后归还给池子,而不是直接关闭。这样可以大幅度减少创建和销毁连接的开销,提高系统性能和并发能力;对于POOLED数据源,还有许多与连接池相关的属性,如最大连接数、最小空闲连接数、连接超时时间、空闲超时时间、侦测查询等等,具体取决于所使用的连接池实现。
  • JNDI: 通过Java Naming and Directory Interface (JNDI)从外部容器(如Java EE应用服务器)获取数据源。这种方式适用于部署在企业级应用服务器上的应用程序,将数据源配置和管理交给容器,使得应用本身与具体的数据库连接细节解耦,便于管理和配置。在配置中通常只需指定JNDI资源的名称。

MyBatis也支持第三方数据源。例如使用DBCP数据源,需要提供一个自定义的DataSourceFactory用于构建数据源对象

    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-dbcp2</artifactId>
      <version>2.7.0</version>
    </dependency>
/**
 * 使用DBCP数据源工厂类,实现DataSourceFactory接口。
 * 主要用于创建和配置数据库连接池。
 */
package com.ssm.Utils;

import org.apache.ibatis.datasource.DataSourceFactory;
import javax.sql.DataSource;
import java.util.Properties;
import org.apache.commons.dbcp2.BasicDataSourceFactory;

public class DbcpDataSourceFactory implements DataSourceFactory{
    // 存储数据源配置属性
    private Properties properties;
    
    /**
     * 设置数据源配置属性。
     * @param properties 数据源的配置属性,包括数据库连接相关的配置。
     */
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
    
    /**
     * 根据配置属性创建并返回DataSource实例。
     * @return 返回配置好的DataSource实例,如果创建失败则返回null。
     */
    public DataSource getDataSource() {
        DataSource dataSource = null;
        // 尝试根据配置属性创建DataSource实例
        try{
            dataSource = BasicDataSourceFactory.createDataSource(properties);
        }catch (Exception e){
            // 如果创建过程中出现异常,则打印异常堆栈信息
            e.printStackTrace();
        }
        return dataSource;
    }
}

然后将这个工厂类配置进去即可

  <!-- 数据源配置,使用连接池 -->
<dataSource type="com.ssm.Utils.DbcpDataSourceFactory">

databaseIdProvider

如果公司默认使用MySQL,但客户坚持用Oracle,在移植性方面MyBatis不如Hibernate,它通过提供```databaseIdProvider元素支持这种场景

    <databaseIdProvider type="DB_VENDOR">
        <property name="Oracle" value="oracle"/>
        <property name="MySQL" value="mysql"/>
        <property name="DB2" value="db2"/>
    </databaseIdProvider>

其中name是数据库的产品名,value是配置的别名

在MyBatis中可以通过这个别名标识一条SQL语句用于哪个数据库

    <!-- 插入一个新的Role记录 -->
    <insert id="insertRole" parameterType="com.ssm.pojo.Role" databaseId="mysql">
        INSERT INTO t_role(roleName, note) VALUES (#{roleName}, #{note})
    </insert>

    <!-- 根据ID查询Role信息,返回Role对象 -->
    <select id="getRoleById" parameterType="int" resultMap="roleMap" databaseId="oracle">
        SELECT id, roleName, note FROM t_role WHERE id = #{id}
    </select>

    <select id="findRoles" parameterType="string" resultMap="roleMap" databaseId="db2">
        select id, roleName, note from t_role where roleName like concat ('%',#{roleName, jdbcType=VARCHAR, javaType=String},'%')
    </select>

当然如果使用了这种方式,在数据源相关配置也要配合好,保证数据源配置无误,比如要配好Oracle、DB2、MySQL的数据源连接;这里边有个逻辑,如果配置了databaseId,则MySQL会选取匹配的databaseId所在的SQL执行,如果没有匹配的databaseId则会选取没有databaseId这个选项的SQL执行,倘若还是匹配失败,则会抛出异常

跟其他的配置一样,同样可以自定义,看一下源码实现逻辑可以写自己的规则,但属实不常用,没多大花精力的必要

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Java EE互联网轻量级框架整合开发的其中一种方式是使用SSM框架(Spring MVC、Spring、MyBatis)和Redis。 首先,SSM框架是一种非常流行的互联网开发框架,它结合了Spring MVC、Spring和MyBatis的优势。Spring MVC提供了MVC模式的实现,可以帮助我们构建灵活、可扩展的Web应用程序;Spring是一个轻量级的IOC(Inversion of Control)容器,并提供了丰富的企业级功能,如事务管理、安全性等;而MyBatis是一个简单易用的持久层框架,提供了ORM(Object Relational Mapping)和数据库访问的功能。 其次,Redis是一种开源的内存数据库,也是一种缓存数据库。它支持多种数据结构,如字符串、列表、哈希、集合等,以及一些高级功能,如发布/订阅、事务等。Redis的高速读写能力和丰富的功能使其成为互联网应用中常用的缓存数据库。在SSM框架中,我们可以使用Redis来缓存数据库查询结果、session数据等,以提高系统的读写性能和响应速度。 在整合开发中,SSM框架通常会负责处理用户请求和返回响应,通过Spring的IOC容器来管理和注入依赖的对象,而MyBatis则负责与数据库进行交互。我们可以通过配置文件来整合SSM框架和Redis,让它们协同工作。例如,我们可以在Spring的配置文件中配置MyBatis的数据源和Mapper接口,以及Redis的连接池和缓存配置;在Spring MVC的配置文件中配置控制器、视图解析器和拦截器等;同时,我们还可以使用Redis的API来进行缓存数据的读写操作。 总之,通过整合SSM框架和Redis,我们可以充分发挥它们各自的优势,构建高性能、可扩展的互联网应用程序。这种整合开发方式可以帮助我们简化开发流程、提高开发效率,并提供更好的用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Davieyang.D.Y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值