目录
一、Mybatis工作原理简单回顾
(一)Mybatis框架分层
(二)原理分析
二、TypeHandler自定义转换类型介绍
(一)基本介绍
(二)注意事项和建议
三、业务使用基本分析展示
(一)自定义TypeHandler实现类
举例一:枚举类型的处理
举例二: LocalDateTime类型的处理
举例三: List 类型的处理
举例四: List 类型的处理
举例五:自定义TimePeriod 类型的处理
举例六: InetAddress 类型的处理
(二)在文件中进行反序列化配置
(三)在文件中进行序列化配置
(四)在mybatis的配置文件中配置typeHandlers
干货分享,感谢您的阅读!
一、Mybatis工作原理简单回顾
(一)Mybatis框架分层
(二)原理分析
MyBatis 是一款优秀的持久层框架,它的核心原理包括以下几个方面:
- 配置文件解析:MyBatis 使用 XML 配置文件来描述数据库连接、SQL 映射关系、缓存等信息。在启动时,MyBatis 会解析配置文件,将配置信息加载到内存中,并构建相应的对象模型来表示这些配置。
- SqlSessionFactory:SqlSessionFactory 是 MyBatis 的核心接口,它是用于创建 SqlSession 的工厂。SqlSessionFactory 是基于配置文件构建的,通过读取配置文件中的信息,创建出 SqlSessionFactory 对象,以供后续的数据库操作使用。
- SqlSession:SqlSession 是 MyBatis 的核心接口之一,它提供了执行 SQL 语句、提交事务、关闭连接等数据库操作的方法。SqlSession 是基于 SqlSessionFactory 创建的,每个 SqlSession 对象都代表一个对数据库的会话,它提供了对数据库的各种操作方法。
- 映射器(Mapper)接口:在 MyBatis 中,我们可以使用接口来定义 SQL 映射关系,这些接口被称为映射器(Mapper)接口。映射器接口通过注解或 XML 配置与具体的 SQL 语句进行绑定,并提供了对数据库的操作方法。MyBatis 会为这些接口动态生成实现类,从而实现对数据库的访问。
- SQL 语句的解析与执行:MyBatis 通过解析映射文件或注解中的 SQL 语句,将其转换为对应的数据库操作。它支持动态 SQL,可以根据条件生成不同的 SQL 语句,从而实现更灵活的数据库操作。
- 参数处理:MyBatis 提供了对 SQL 语句中参数的处理支持。它可以根据参数的类型、注解或配置文件中的映射规则,将 Java 对象与 SQL 语句中的参数进行自动转换。
- 结果集映射:MyBatis 支持将查询结果映射为 Java 对象。通过配置文件或注解,可以定义查询结果与 Java 对象之间的映射关系,包括属性名、类型转换、关联关系等。MyBatis 使用反射机制将查询结果转换为对应的 Java 对象。
- 一级缓存和二级缓存:MyBatis 提供了缓存机制来提高数据库访问的性能。一级缓存是 SqlSession 级别的缓存,它缓存了查询的结果对象。二级缓存是全局级别的缓存,它可以被多个 SqlSession 共享,提供了跨会话的缓存功能。
- 事务管理:MyBatis 支持事务管理,可以通过配置文件或注解来指定事务的隔离级别、传播行为以及异常回滚规则。在进行数据库操作时,MyBatis 会根据配置的事务管理方式来开启、提交或回滚事务,确保数据的一致性和完整性。
- 插件机制:MyBatis 提供了插件机制,可以在 SQL 语句执行的前后进行拦截和扩展。开发者可以编写自定义的插件,并在配置文件中注册,从而对 SQL 的执行过程进行拦截和增强,实现自定义的功能扩展。
总的来说,MyBatis 的核心原理是基于配置文件和接口定义的,通过解析配置文件、创建工厂、会话管理和数据库操作等步骤来实现与数据库的交互。它提供了灵活的 SQL 映射、参数处理、结果集映射、缓存和事务管理等功能,让开发者能够方便地进行数据库操作。同时,MyBatis 还支持插件机制,提供了扩展性和定制化的能力,使得开发者能够根据具体需求进行功能的增强和定制。
二、TypeHandler自定义转换类型介绍
(一)基本介绍
在 Java 开发中,TypeHandler 是 MyBatis 框架中的一个核心组件,用于实现数据库与 Java 类型之间的相互转换。它允许开发人员自定义类型处理器,以满足特定的业务需求。
TypeHandler 的作用是在 MyBatis 执行 SQL 查询或更新操作时,将数据库中的列值转换为 Java 对象,并在将 Java 对象写入数据库时执行相反的转换。它提供了一种灵活且可扩展的方式,用于处理数据库类型与 Java 类型之间的映射关系。
以下是 TypeHandler 的一些关键特点和使用方式:
- 类型转换:TypeHandler 负责将数据库中的列值转换为 Java 对象,并将 Java 对象转换为数据库可接受的类型。
- 自定义类型处理器:MyBatis 提供了一些默认的类型处理器,例如处理整数、字符串、日期等常见类型。同时,开发人员可以根据需要自定义类型处理器,实现特定类型的转换逻辑。
- 注册类型处理器:自定义的类型处理器需要在 MyBatis 的配置文件中进行注册,以便 MyBatis 在执行数据库操作时能够找到并使用它们。
- 支持复杂类型:TypeHandler 不仅可以处理基本类型,还可以处理复杂类型,例如枚举、自定义对象等。
- 映射规则:TypeHandler 的使用是基于映射规则的,即在 MyBatis 的映射文件中,通过指定列和属性之间的映射关系,TypeHandler 才能正确地进行类型转换。
(二)注意事项和建议
在 MyBatis 中使用自定义 TypeHandler 进行类型转换时,还有一些注意事项和建议:
- 数据库字段与 Java 对象属性类型的匹配:确保数据库字段的类型与 Java 对象属性的类型匹配。如果数据库字段的类型与自定义 TypeHandler 处理的 Java 对象类型不一致,可能会导致转换错误或数据丢失。
- 处理 NULL 值:在自定义 TypeHandler 中处理 NULL 值是很重要的。可以通过判断传入的参数或结果集中的值是否为 NULL,并在处理过程中进行相应的处理。例如,可以将 NULL 值转换为 Java 对象的默认值,或者通过特定的标识值来表示 NULL。
- 注册 TypeHandler:确保在 MyBatis 的配置文件中正确注册自定义的 TypeHandler。如果没有正确注册,MyBatis 将无法找到并使用该 TypeHandler,导致类型转换失败。
- 测试和验证:在使用自定义 TypeHandler 进行类型转换之前,建议进行充分的测试和验证。确保自定义 TypeHandler 能够正确地将数据库类型转换为相应的 Java 对象类型,并且在查询和更新操作中能够正常工作。
- 了解官方提供的默认 TypeHandler:MyBatis 提供了一些默认的 TypeHandler,用于常见的数据类型转换,如字符串、整数、日期等。在自定义 TypeHandler 之前,建议先查看官方文档,了解是否已经有现成的 TypeHandler 可以满足你的需求。
- 可重用性和扩展性:在设计自定义 TypeHandler 时,考虑其可重用性和扩展性。如果有多个字段需要进行相同的类型转换,可以将转换逻辑封装在一个通用的 TypeHandler 中,并在映射文件中多次引用它。另外,可以通过继承或组合的方式扩展已有的 TypeHandler,以满足不同的业务需求。
- 注意线程安全性:自定义 TypeHandler 应该是线程安全的,因为 MyBatis 在多线程环境下可能会同时使用同一个 TypeHandler 实例进行类型转换。确保在实现自定义 TypeHandler 时考虑线程安全性,并采取相应的线程安全措施,如使用线程局部变量或同步机制。
以上是关于使用自定义 TypeHandler 进行类型转换的注意事项和建议。根据具体的业务需求,可以根据以上的指导来创建适合自己的自定义 TypeHandler,并在 MyBatis 中应用它。但是如果害怕出错还是建议直接使用默认的来处理即可,相关不合理的可能就直接通过查询或插入时进行转换了。
三、业务使用基本分析展示
(一)自定义TypeHandler实现类
自定义TypeHandler实现类,通常有两种方式:实现TypeHandler接口;继承BaseTypeHandler类。以扩展BaseTypeHandler类为例,说明如何实现想要转换成的数据库对象的具体逻辑。
举例一:枚举类型的处理
假设我们有一个订单实体类 Order,其中包含一个 Status 枚举类型的状态字段,在数据库中,我们将状态字段存储为一个字符串类型的列,例如 status。
package ;
/**
* @author yanfengzhang
* @description 订单实体类 Order
* 其中包含一个 Status 枚举类型
* @date 2023/5/13 23:34
*/
public enum Status {
CREATED,
PROCESSING,
COMPLETED,
CANCELLED
}
我们可以使用自定义的 TypeHandler 来实现 Status 枚举类型与数据库列值之间的转换。以下是一个示例的 TypeHandler 实现:
package ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
* @author yanfengzhang
* @description 自定义的 TypeHandler 来实现 Status 枚举类型与数据库列值之间的转换
* @date 2023/5/13 23:36
*/
public class StatusTypeHandler extends BaseTypeHandler<Status> {
@Override
public Status getNullableResult(ResultSet rs, String columnName) throws SQLException {
// 从 ResultSet 中获取列值
int data = (columnName);
// 将列值转换为枚举对象
return (data);
}
@Override
public Status getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
int data = (columnIndex);
return (data);
}
@Override
public Status getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
int data = (columnIndex);
return (data);
}
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Status status, JdbcType jdbcType) throws SQLException {
// 将枚举值的code存储到数据库中
(i, ());
}
}
在上述示例中,我们自定义了一个 TypeHandler 类 StatusTypeHandler,用于处理 Status 枚举类型与数据库列值之间的转换。我们需要继承 BaseTypeHandler 并重写相应的方法来实现转换逻辑。
举例二: LocalDateTime类型的处理
假设我们有一个订单实体类 Order,其中包含一个 LocalDateTime 类型的创建时间字段,在数据库中,我们将创建时间字段存储为一个 DATETIME 类型的列,例如 create_time。
我们可以使用自定义的 TypeHandler 来实现 LocalDateTime 类型与数据库列值之间的转换。以下是一个示例的 TypeHandler 实现:
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
* @author yanfengzhang
* @description 自定义的 TypeHandler 来实现 LocalDateTime 类型与数据库列值之间的转换
* @date 2023/5/14 10:28
*/
public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType) throws SQLException {
// 将 LocalDateTime 转换为 Timestamp 并设置到 PreparedStatement 中
(i, (parameter));
}
@Override
public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
// 从 ResultSet 中获取 Timestamp 值
Timestamp timestamp = (columnName);
// 将 Timestamp 转换为 LocalDateTime
return timestamp != null ? () : null;
}
@Override
public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
Timestamp timestamp = (columnIndex);
return timestamp != null ? () : null;
}
@Override
public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
Timestamp timestamp = (columnIndex);
return timestamp != null ? () : null;
}
}
在上述示例中,我们自定义了一个 TypeHandler 类 LocalDateTimeTypeHandler,用于处理 LocalDateTime 类型与数据库列值之间的转换。我们需要继承 BaseTypeHandler 并重写相应的方法来实现转换逻辑。
现在,当使用 MyBatis 进行数据库查询时,会自动将数据库中的列值转换为对应的 LocalDateTime 对象,使我们能够方便地在 Java 代码中使用 LocalDateTime 类型。
举例三: List<String> 类型的处理
当处理 List 类型时,可以使用自定义的 TypeHandler 将 List 对象转换为字符串进行存储,或者从数据库中读取字符串并还原为 List 对象。
假设有一个订单实体类 Order,其中包含一个 List<String> 类型的商品列表字段(products),我们可以使用自定义的 TypeHandler 来实现 List<String> 类型与数据库列值之间的转换。以下是一个示例的 TypeHandler 实现:
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
* @author yanfengzhang
* @description 自定义的 TypeHandler 来实现 List<String> 类型与数据库列值之间的转换
* @date 2023/5/14 10:39
*/
public class StringListTypeHandler extends BaseTypeHandler<List<String>> {
private static final String DELIMITER = ",";
@Override
public void setNonNullParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {
// 将 List<String> 转换为以逗号分隔的字符串
String rolesString = (DELIMITER, parameter);
// 将字符串存储到数据库中
(i, rolesString);
}
@Override
public List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException {
// 从 ResultSet 中获取字符串值
String rolesString = (columnName);
// 将字符串转换为 List<String>
return parseRolesString(rolesString);
}
@Override
public List<String> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String rolesString = (columnIndex);
return parseRolesString(rolesString);
}
@Override
public List<String> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String rolesString = (columnIndex);
return parseRolesString(rolesString);
}
private List<String> parseRolesString(String rolesString) {
if (rolesString == null || ()) {
return ();
}
// 将字符串按逗号分隔转换为 List<String>
return ((DELIMITER));
}
}
在上述示例中,我们自定义了一个 TypeHandler 类 StringListTypeHandler,用于处理 List<String> 类型与数据库列值之间的转换。我们需要继承 BaseTypeHandler 并重写相应的方法来实现转换逻辑。现在,当使用 MyBatis 进行数据库查询时,会自动将数据库中的列值转换为对应的 List<String> 对象,使我们能够方便地在 Java 代码中使用 List<String> 类型。
举例四: List<Long> 类型的处理
同样的,可以自定义 TypeHandler 中将 VARCHAR 类型的数据库列与 List<Long> 类型的 Java 对象进行转换
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
* @author yanfengzhang
* @description 自定义 TypeHandler 中将 VARCHAR 类型的数据库列与 List<Long> 类型的 Java 对象进行转换
* @date 2023/5/14 10:48
*/
public class LongListTypeHandler extends BaseTypeHandler<List<Long>> {
private static final String DELIMITER = ",";
@Override
public void setNonNullParameter(PreparedStatement ps, int i, List<Long> parameter, JdbcType jdbcType) throws SQLException {
String joinedString = ().map(String::valueOf).collect((DELIMITER));
(i, joinedString);
}
@Override
public List<Long> getNullableResult(ResultSet rs, String columnName) throws SQLException {
String columnValue = (columnName);
return convertStringToList(columnValue);
}
@Override
public List<Long> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String columnValue = (columnIndex);
return convertStringToList(columnValue);
}
@Override
public List<Long> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String columnValue = (columnIndex);
return convertStringToList(columnValue);
}
private List<Long> convertStringToList(String columnValue) {
if (columnValue == null || ()) {
return null;
}
List<Long> resultList = new ArrayList<>();
String[] values = (DELIMITER);
for (String value : values) {
((value));
}
return resultList;
}
}
举例五:自定义TimePeriod 类型的处理
同样的,可以自定义 TypeHandler 中将数据库中的时间段(以秒为单位)与 Java 对象中的 TimePeriod 类型进行转换。
首先,定义 TimePeriod 类表示时间段:
package ;
import ;
import ;
/**
* @author yanfengzhang
* @description
* @date 2023/5/14 10:58
*/
@Data
public class TimePeriod {
private LocalDateTime startTime;
private LocalDateTime endTime;
public TimePeriod(LocalDateTime startTime, LocalDateTime endTime) {
= startTime;
= endTime;
}
}
然后,定义 TimePeriodTypeHandler 类实现 TypeHandler 接口来处理 TimePeriod 类型的转换:
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
* @author yanfengzhang
* @description 自定义 TypeHandler 中将数据库中的时间段(以秒为单位)与 Java 对象中的 TimePeriod 类型进行转换
* @date 2023/5/14 10:52
*/
public class TimePeriodTypeHandler implements TypeHandler<TimePeriod> {
@Override
public void setParameter(PreparedStatement ps, int i, TimePeriod parameter, JdbcType jdbcType) throws SQLException {
// 将 Java 对象转换为数据库类型,并设置到 PreparedStatement 中的指定参数位置
if (parameter != null) {
(i, (()));
(i + 1, (()));
} else {
(i, );
(i + 1, );
}
}
@Override
public TimePeriod getResult(ResultSet rs, String columnName) throws SQLException {
// 从 ResultSet 中获取指定列名的数据库值,并将其转换为对应的 Java 对象
LocalDateTime startTime = (columnName).toLocalDateTime();
LocalDateTime endTime = (columnName + "_end").toLocalDateTime();
return new TimePeriod(startTime, endTime);
}
@Override
public TimePeriod getResult(ResultSet rs, int columnIndex) throws SQLException {
// 从 ResultSet 中获取指定列索引的数据库值,并将其转换为对应的 Java 对象
LocalDateTime startTime = (columnIndex).toLocalDateTime();
LocalDateTime endTime = (columnIndex + 1).toLocalDateTime();
return new TimePeriod(startTime, endTime);
}
@Override
public TimePeriod getResult(CallableStatement cs, int columnIndex) throws SQLException {
// 从 CallableStatement 中获取指定列索引的数据库值,并将其转换为对应的 Java 对象
LocalDateTime startTime = (columnIndex).toLocalDateTime();
LocalDateTime endTime = (columnIndex + 1).toLocalDateTime();
return new TimePeriod(startTime, endTime);
}
}
举例六: InetAddress 类型的处理
假设我们有一个订单实体类 Order,其中包含一个 InetAddress 类型的 IP 地址字段,在数据库中,我们将 IP 地址存储为一个字符串类型的列,例如 ip_address。
我们可以使用自定义的 TypeHandler 来实现 InetAddress 类型与数据库列值之间的转换。以下是一个示例的 TypeHandler 实现:
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
* @author yanfengzhang
* @description 用于处理 InetAddress 类型与数据库列值之间的转换
* @date 2023/5/14 11:20
*/
public class InetAddressTypeHandler extends BaseTypeHandler<InetAddress> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, InetAddress parameter, JdbcType jdbcType) throws SQLException {
// 获取 IP 地址的字符串表示
String ipAddressString = ();
// 将字符串存储到数据库中
(i, ipAddressString);
}
@Override
public InetAddress getNullableResult(ResultSet rs, String columnName) throws SQLException {
// 从 ResultSet 中获取字符串值
String ipAddressString = (columnName);
// 将字符串转换为 InetAddress
return parseIpAddressString(ipAddressString);
}
@Override
public InetAddress getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String ipAddressString = (columnIndex);
return parseIpAddressString(ipAddressString);
}
@Override
public InetAddress getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String ipAddressString = (columnIndex);
return parseIpAddressString(ipAddressString);
}
private InetAddress parseIpAddressString(String ipAddressString) {
try {
// 将字符串解析为 InetAddress
return (ipAddressString);
} catch (UnknownHostException e) {
// 处理解析错误,例如返回默认的 IP 地址或抛出异常
return null;
}
}
}
(二)在文件中进行反序列化配置
该写法只对返回此resultMap的查询语句有效
<resultMap type="">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="status" jdbcType="INTEGER" property="status"
typeHandler="" />
<result column="limit_period" jdbcType="VARCHAR" property="limitPeriod"
typeHandler=""/>
<result column="create_time" jdbcType="DATETIME" property="createTime"
typeHandler=""/>
<result column="sku_ids" jdbcType="VARCHAR" property="skuIds"
typeHandler=""/>/>
<result column="products" jdbcType="VARCHAR" property="products"
typeHandler=""/>
<result column="ip_address" jdbcType="VARCHAR" property="ipAddress"
typeHandler=""/>
<result column="cname" jdbcType="VARCHAR" property="cname"/>
<result column="uname" jdbcType="VARCHAR" property="uname"/>
</resultMap>
(三)在文件中进行序列化配置
如果希望在写入数据库的时候,按照指定规则对写入对象进行序列化,则需要在写入sql语句处显式指定typeHandler
<update parameterType="map" >
UPDATE
<include ref/>
SET
name = #{},
limit_period = #{, typeHandler=},
sku_ids =#{, typeHandler=},
uname = #{},
utime = unix_timestamp()
where id = #{}
</update>
(四)在mybatis的配置文件中配置typeHandlers
方法一:启动时会扫描包下的所有文件
<typeHandlers>
<package name=""/>
</typeHandlers>
方法二:或单个写入要注册的TypeHandler
<typeHandlers>
<typeHandler handler=""
javaType=""
jdbcType="VARCHAR"/>
<typeHandler handler=""
javaType=""
jdbcType="VARCHAR"/>
</typeHandlers>
参考文章链接
- Mybatis之工作原理_mybatis 的原理_Javxuan的博客-****博客
- MyBatis 官方文档 - TypeHandlers:MyBatis 官方文档中关于 TypeHandler 的详细说明和使用示例。
- MyBatis TypeHandler 使用详解:一篇博客文章介绍了如何使用 MyBatis 中的 TypeHandler,并提供了详细的示例和说明。
- MyBatis TypeHandler 的自定义与应用:该文章介绍了如何自定义 TypeHandler,并通过示例演示了各种类型转换的场景。
- MyBatis 之 TypeHandler 实现原理:一篇知乎专栏文章,深入解析了 MyBatis 中的 TypeHandler 实现原理和相关的核心类。
- MyBatis 之 TypeHandler 源码解析:该文章通过分析 MyBatis 中的 TypeHandler 源码,解释了其内部工作原理和实现细节。