23. 如何在MyBatis中处理枚举类型?有哪些常见的处理方式?json 17. 什么是MyBatis中的TypeHandler?如何自定义TypeHandler处理复杂类型?
在 MyBatis 中,处理枚举类型有多种方式,通常取决于枚举在数据库中的存储形式以及你希望在 Java 中如何使用这些枚举类型。常见的处理方式包括使用 EnumTypeHandler、自定义 TypeHandler,或通过直接在枚举中定义转换方法来实现。
1. 使用 MyBatis 内置的 EnumTypeHandler
MyBatis 提供了内置的 EnumTypeHandler,可以用于将枚举类型映射为数据库中的整数或字符串。
1.1 枚举类型映射为字符串
假设你有一个枚举 Status,希望将其映射为数据库中的字符串:
-
public enum Status {
-
ACTIVE, INACTIVE, DELETED;
-
}
对应的数据库表结构:
-
CREATE TABLE user (
-
id INT PRIMARY KEY,
-
name VARCHAR(50),
-
status VARCHAR(10) -- 存储枚举值的字符串形式
-
);
在 MyBatis 中,你可以使用 EnumTypeHandler 自动处理这个映射:
配置 EnumTypeHandler:
你可以在 mybatis-config.xml 中全局配置 EnumTypeHandler:
-
<typeHandlers>
-
<typeHandler handler="org.apache.ibatis.type.EnumTypeHandler" javaType="com.example.Status" jdbcType="VARCHAR"/>
-
</typeHandlers>
或者在 Mapper 文件中为特定字段配置 EnumTypeHandler:
-
<select id="selectUserById" resultType="User">
-
SELECT id, name, status
-
FROM user
-
WHERE id = #{id}
-
</select>
-
-
<resultMap id="UserResultMap" type="User">
-
<id property="id" column="id"/>
-
<result property="name" column="name"/>
-
<result property="status" column="status" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
-
</resultMap>
在这种情况下,MyBatis 会将数据库中的字符串值自动映射到对应的 Status 枚举类型。
1.2 枚举类型映射为整数
如果你希望将枚举映射为整数,可以在枚举中定义数值,然后使用 EnumOrdinalTypeHandler:
-
public enum Status {
-
ACTIVE(1), INACTIVE(2), DELETED(3);
-
-
private final int value;
-
-
Status(int value) {
-
this.value = value;
-
}
-
-
public int getValue() {
-
return value;
-
}
-
}
对应的数据库表结构:
-
CREATE TABLE user (
-
id INT PRIMARY KEY,
-
name VARCHAR(50),
-
status INT -- 存储枚举值的整数形式
-
);
配置 EnumOrdinalTypeHandler:
-
<typeHandlers>
-
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.example.Status" jdbcType="INTEGER"/>
-
</typeHandlers>
这会将枚举的 ordinal 值(即枚举在定义中的顺序)存储到数据库,并在查询时将整数转换回枚举类型。
2. 使用自定义 TypeHandler
如果你有更复杂的需求,比如枚举对应的数据库值并不是 ordinal 或 name,而是自定义的某个值(如数据库中的状态码),你可以自定义一个 TypeHandler 来处理这些映射。
示例:自定义 TypeHandler 映射枚举到状态码
假设你有一个 Status 枚举,它对应的数据库字段是一个状态码,而不是 ordinal 或 name:
-
public enum Status {
-
ACTIVE(1), INACTIVE(0), DELETED(-1);
-
-
private final int code;
-
-
Status(int code) {
-
this.code = code;
-
}
-
-
public int getCode() {
-
return code;
-
}
-
-
public static Status fromCode(int code) {
-
for (Status status : Status.values()) {
-
if (status.getCode() == code) {
-
return status;
-
}
-
}
-
throw new IllegalArgumentException("Unknown status code: " + code);
-
}
-
}
创建自定义 TypeHandler:
-
import org.apache.ibatis.type.BaseTypeHandler;
-
import org.apache.ibatis.type.JdbcType;
-
-
import java.sql.*;
-
-
public class StatusTypeHandler extends BaseTypeHandler<Status> {
-
-
-
public void setNonNullParameter(PreparedStatement ps, int i, Status status, JdbcType jdbcType) throws SQLException {
-
ps.setInt(i, status.getCode());
-
}
-
-
-
public Status getNullableResult(ResultSet rs, String columnName) throws SQLException {
-
int code = rs.getInt(columnName);
-
return Status.fromCode(code);
-
}
-
-
-
public Status getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-
int code = rs.getInt(columnIndex);
-
return Status.fromCode(code);
-
}
-
-
-
public Status getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
-
int code = cs.getInt(columnIndex);
-
return Status.fromCode(code);
-
}
-
}
注册自定义 TypeHandler:
在 mybatis-config.xml 中注册自定义的 TypeHandler:
-
<typeHandlers>
-
<typeHandler handler="com.example.StatusTypeHandler" javaType="com.example.Status" jdbcType="INTEGER"/>
-
</typeHandlers>
在 Mapper 中使用自定义 TypeHandler:
-
<resultMap id="UserResultMap" type="User">
-
<id property="id" column="id"/>
-
<result property="name" column="name"/>
-
<result property="status" column="status" typeHandler="com.example.StatusTypeHandler"/>
-
</resultMap>
3. 通过枚举类本身处理映射
另一种方式是在枚举类中直接定义用于数据库存储的值,并提供从数据库值到枚举实例的转换方法。这种方式使得枚举的映射逻辑集中在枚举类中,简化了 TypeHandler 的使用。
示例:
-
public enum Status {
-
ACTIVE(1), INACTIVE(0), DELETED(-1);
-
-
private final int code;
-
-
Status(int code) {
-
this.code = code;
-
}
-
-
public int getCode() {
-
return code;
-
}
-
-
public static Status fromCode(int code) {
-
for (Status status : Status.values()) {
-
if (status.getCode() == code) {
-
return status;
-
}
-
}
-
throw new IllegalArgumentException("Unknown status code: " + code);
-
}
-
}
在 Mapper XML 文件中直接使用 #{status.code}:
-
<insert id="insertUser" parameterType="User">
-
INSERT INTO user (id, name, status)
-
VALUES (#{id}, #{name}, #{status.code})
-
</insert>
-
-
<select id="selectUserById" resultMap="UserResultMap">
-
SELECT id, name, status
-
FROM user
-
WHERE id = #{id}
-
</select>
在这个例子中,MyBatis 会自动调用 Status 枚举的 getCode() 方法来获取数据库中存储的值,然后使用 fromCode() 方法将数据库值转换回枚举实例。
总结
-
EnumTypeHandler和EnumOrdinalTypeHandler:MyBatis 内置的处理器,适用于枚举映射为字符串或整数的情况。 -
自定义
TypeHandler:当你需要更复杂的映射逻辑时,自定义TypeHandler是一个灵活的解决方案。 -
在枚举类中处理映射:通过在枚举类中定义用于数据库存储的值和从数据库值转换回枚举的方法,可以简化 MyBatis 映射配置。
根据具体的需求和场景,可以选择最适合的方式来处理枚举类型在 MyBatis 中的映射。
通过研究MyBatis-Plus官网
多数据源支持
dynamic-datasource 是一个开源的 Spring Boot 多数据源启动器,提供了丰富的功能,包括数据源分组、敏感信息加密、独立初始化表结构等。
使用方法:
-
引入依赖
-
<!--多个数据源-->
-
<dependency>
-
<groupId>com.baomidou</groupId>
-
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
-
<version>3.2.0</version>
-
</dependency>
-
<dependency>
-
<groupId>com.baomidou</groupId>
-
<artifactId>mybatis-plus-boot-starter</artifactId>
-
<version>3.2.0</version>
-
</dependency>
代码解读
注意:项目中已经存在的mybatis的依赖需要注释,防止下面的共同存在的mybatis-spring产生冲突
-
配置数据源:如
-
spring.datasource.dynamic.primary=db1
-
spring.datasource.dynamic.strict=false
-
spring.datasource.dynamic.datasource.db1.url=jdbc:mysql://localhost:3306/ican?characterEncoding=utf-8&useUnicode=true&serverTimezone=UTC&allowMultiQueries=true
-
spring.datasource.dynamic.datasource.db1.username=root
-
spring.datasource.dynamic.datasource.db1.password=123456
-
spring.datasource.dynamic.datasource.db1.driver-class-name=com.mysql.cj.jdbc.Driver
-
-
spring.datasource.dynamic.datasource.db2.url=jdbc:mysql://192.168.21.37:3306/210_bm_inventory?useUnicode=true&allowPublicKeyRetrieval=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true
-
spring.datasource.dynamic.datasource.db2.username=root
-
spring.datasource.dynamic.datasource.db2.password=root123
-
spring.datasource.dynamic.datasource.db2.driver-class-name=com.mysql.cj.jdbc.Driver
一共配置了两个数据源 db1和db2 其中spring.datasource.dynamic.primary=db1配置主数据源为db1(没有注解时默认为主数据源)
spring.datasource.dynamic.strict=false 这个设置的是严格模式,true为开启,false为关闭。
开启@DS(db3) db3并不存在会直接报错,不开启则会采用主数据源进行二次尝试
-
使用
@DS切换数据源:
-
-
-
public interface InventoryDao {
-
-
-
List<Inventory> select(InventoryQuery query);
-
Integer insert(Inventory inventory);
-
Integer insertBatch(List<Inventory> inventorys);
-
Integer update(Integer id, int optionQty, int version,String lastUpdateBy);
-
Integer delete(Integer id);
-
}
类或者方法上都能加注释,注释括号里可以为组名(也就是2里面配置的db1,db2)也可以为具体某个库的名称。
目前主要用于wms仓储系统直接操作北猫商城里的inventory表。

如果引入不了依赖选择删除maven库中的具体文件夹或者依赖冲突可以选择直接删除总的maven库,或者解决冲突:进入pom

红线的就是有冲突的包

可以根据手动删除依赖、在Plugins下载Maven Helper插件进行解决、或者配置冲突子类不启动
在现代应用中,乐观锁(Optimistic Locking)是解决并发问题的重要机制。它通过在数据更新时验证数据版本来确保数据的一致性,从而避免并发冲突。与悲观锁不同,乐观锁并不依赖数据库的锁机制,而是通过检查数据的版本或标志字段来判断数据是否被其他事务修改过。
MyBatis-Plus 提供了便捷的乐观锁支持,通过在实体类中添加版本号字段(通常是一个 int 或 long 类型的字段),并在更新操作时检查版本号,以确保数据的一致性和完整性,同时不影响系统的并发性能。
然而,MyBatis-Plus 本身并没有内置的重试机制来处理乐观锁失败的情况。
MyBatis-Plus 乐观锁的具体实现_java_脚本之家 (jb51.net)乐观锁的具体实现
乐观锁的工作原理
-
版本号字段:在实体类中添加一个版本号字段,通常命名为
version。 -
更新操作:在更新操作时,增加一个条件,检查版本号是否匹配。如果不匹配,表示数据已经被其他事务修改,更新操作失败
重要:

实现重试机制
1. 自定义 Service 方法
在服务层中手动实现重试逻辑。
1.引入依赖
-
<dependency>
-
<groupId>org.springframework.boot</groupId>
-
<artifactId>spring-boot-starter-web</artifactId>
-
</dependency>
-
<dependency>
-
<groupId>mysql</groupId>
-
<artifactId>mysql-connector-java</artifactId>
-
<scope>runtime</scope>
-
</dependency>
-
<dependency>
-
<groupId>org.springframework.boot</groupId>
-
<artifactId>spring-boot-starter-test</artifactId>
-
<scope>test</scope>
-
</dependency>
-
-
<dependency>
-
<groupId>com.baomidou</groupId>
-
<artifactId>mybatis-plus-boot-starter</artifactId>
-
<version>3.5.7</version>
-
</dependency>
2.config层
-
-
//@MapperScan("com.beiyou.dao")
-
public class MybatisPlusConfig {
-
-
-
public MybatisPlusInterceptor mybatisPlusInterceptor() {
-
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
-
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
-
System.out.println("MybatisPlusInterceptor");
-
return mybatisPlusInterceptor;
-
}
-
-
}
3.dao层
-
-
public interface AccountDao extends BaseMapper<Account> {
-
-
-
}
4.entity层
-
-
-
-
-
-
public class Account {
-
-
-
private Integer id;
-
-
private Integer balance;
-
// 用于mybatis-plus
-
private Integer version; // 用于乐观锁
-
}
5.service层
-
-
public class AccountService {
-
-
-
private AccountDao accountDao;
-
-
private static final int MAX_RETRIES = 3;
-
private static final long RETRY_DELAY_MILLIS = 100;
-
-
public void updateAccountBalance() {
-
Thread thread = new Thread(new Runnable() {
-
-
public void run() {
-
int retryCount = 0;
-
while (retryCount <= MAX_RETRIES) {
-
Account account = accountDao.selectById(1);
-
if (account == null) {
-
System.out.println("Account not found");
-
return;
-
}
-
account.setBalance(2);
-
int update = accountDao.updateById(account);
-
-
if (update > 0) {
-
System.out.println("更新成功");
-
return;
-
}
-
retryCount++;
-
// 版本号不匹配,乐观锁失败
-
if (retryCount <= MAX_RETRIES) {
-
try {
-
System.out.println("Optimistic lock failed, retrying... (attempt " + retryCount + ")");
-
TimeUnit.MILLISECONDS.sleep(RETRY_DELAY_MILLIS);
-
} catch (InterruptedException ex) {
-
Thread.currentThread().interrupt();
-
throw new MybatisPlusException("Thread interrupted during retry", ex);
-
}
-
continue;
-
}
-
-
-
}
-
System.out.println("Max retries reached, update failed accoutService");
-
}
-
});
-
-
thread.start();
-
}
-
-
-
}
6.test
-
-
private AccountService accountService;
-
-
-
//自定义 Service 方法
-
-
public void testAccountService() throws InterruptedException {
-
ExecutorService executorService = Executors.newFixedThreadPool(10);
-
for (int i = 0; i < 15; i++) {
-
executorService.submit(accountService::updateAccountBalance);
-
}
-
try { // 阻塞主线程,防止进程退出
-
System.in.read();
-
} catch (IOException e) {
-
throw new RuntimeException(e);
-
}
-
}
2. 使用 AOP 实现重试
你可以使用 Spring AOP 来实现重试逻辑,通过切面编程在方法调用前后进行干预。
1.引入依赖
-
-
<dependency>
-
<groupId>org.springframework.boot</groupId>
-
<artifactId>spring-boot-starter-aop</artifactId>
-
</dependency>
2.config层
同上
3.dao层
同上
4.entity层
同上
5.service层
-
-
public class AopService {
-
-
private static final Logger logger = LoggerFactory.getLogger(AopService.class);
-
-
-
private AccountDao accountDao;
-
-
public boolean updateAccountBalance(int newBalance) {
-
Account account = accountDao.selectById(1);
-
if (account == null) {
-
logger.warn("Account not found: {}", 1);
-
return false;
-
}
-
account.setBalance(newBalance);
-
int update = accountDao.updateById(account);
-
-
if (update > 0) {
-
logger.info("Account updated successfully: {}", account);
-
return true;
-
} else {
-
logger.warn("No rows updated for account: {}", account);
-
throw new OptimisticLockingFailureException("重试");
-
// return false;
-
}
-
-
}
-
}
6.test
-
-
private AopService aopService;
-
//aop方式
-
-
public void testAopService() throws InterruptedException {
-
ExecutorService executorService = Executors.newFixedThreadPool(5);
-
for (int i = 0; i < 15; i++) {
-
executorService.submit(() -> {
-
aopService.updateAccountBalance(13);
-
});
-
}
-
try {
-
System.in.read();
-
} catch (IOException e) {
-
throw new RuntimeException(e);
-
}
-
}
3. 使用 spring-retry 库
你可以使用spring-retry库来实现重试机制。spring-retry提供了方便的注解和配置,可以轻松地实现重试逻辑。注意事项:spring-retry由于是基于AOP实现,所以不支持类里自调用方法。注意:需要在启动类上加@EnableRetry开启spring-retry库
1.引入依赖
-
<dependency>
-
<groupId>org.springframework.retry</groupId>
-
<artifactId>spring-retry</artifactId>
-
<version>1.3.1</version>
-
</dependency>
-
<dependency>
-
<groupId>org.springframework.boot</groupId>
-
<artifactId>spring-boot-starter-aop</artifactId>
-
</dependency>
2.config层
同上
3.dao层
同上
4.entity层
同上
5.service层
-
-
public class RetryService {
-
-
private AccountDao accountDao;
-
-
-
public boolean updateUser() {
-
Account account = accountDao.selectById(1);
-
account.setBalance(12);
-
int update = accountDao.updateById(account);
-
if (update > 0){
-
System.out.println("更新成功");
-
}else {
-
System.out.println("更新失败");
-
throw new OptimisticLockingFailureException("更新失败");
-
}
-
return update > 0;
-
}
-
}
6.test
-
-
private RetryService retryService;
-
-
//使用 spring-retry 库
-
-
void testRetryService() {
-
// 模拟多个线程并发更新同一个用户
-
ExecutorService executorService = Executors.newFixedThreadPool(5);
-
for (int i = 0; i < 20; i++) {
-
executorService.submit(retryService::updateUser);
-
}
-
try {
-
System.in.read();
-
} catch (IOException e) {
-
throw new RuntimeException(e);
-
}
-
}
-
11. MyBatis的一级缓存和二级缓存有什么区别?如何配置和使用二级缓存?
-
10. Java 中的 HashSet 和 HashMap 有什么区别?
-
8. 如何在MyBatis中实现动态SQL?动态SQL有什么用?常见的动态SQL标签有哪些?
-
7. MyBatis中的SqlSession是什么?如何管理SqlSession的生命周期?
-
6. MyBatis中的@Mapper注解和XML映射文件的区别是什么?
-
5. MyBatis 如何实现数据库类型和 Java 类型的转换的?
-
4. MyBatis如何与Spring集成?有哪些常见的配置方式?
-
3. MyBatis 执行原理了解吗?
-
2. MyBatis有哪些优缺点?
-
1. MyBatis是什么?与Hibernate相比有哪些优缺点?
-
Mybatis运行原理
-
Mybatis原生使用
-
Mybatis优秀的持久层框架
TypeHandler是 MyBatis 中的一个接口,用于在 Java 类型和 JDBC 类型之间进行转换。每当 MyBatis 需要将 Java 对象传递给 SQL 语句,或者从 SQL 查询结果中获取数据并转换为 Java 对象时,都会使用TypeHandler。TypeHandler的主要功能包括:-
Java 类型到 JDBC 类型的转换:在 MyBatis 执行 SQL 语句时,将 Java 对象作为参数传递给 SQL 语句时,
TypeHandler负责将 Java 类型的数据转换为 JDBC 类型。 -
JDBC 类型到 Java 类型的转换:在 MyBatis 从数据库中查询数据时,
TypeHandler负责将 JDBC 类型的数据转换为 Java 类型。
MyBatis 内置了一些常用的
TypeHandler,如将String转换为VARCHAR,将Integer转换为INTEGER等,满足了大多数情况下的需求。然而,对于一些复杂或自定义类型的转换,可能需要自定义TypeHandler。如何自定义 TypeHandler 处理复杂类型?
当你需要将数据库中的某种数据类型转换为 Java 中的复杂类型,或反过来时,就需要自定义
TypeHandler。以下是自定义TypeHandler的步骤和示例。1. 实现 TypeHandler 接口
MyBatis 提供了
TypeHandler接口,你可以实现这个接口来自定义类型转换。该接口有四个方法需要实现:-
setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType):将 Java 类型的数据设置到PreparedStatement中,作为 SQL 语句的参数。 -
getResult(ResultSet rs, String columnName):从ResultSet中通过列名获取数据,并将其转换为 Java 类型。 -
getResult(ResultSet rs, int columnIndex):从ResultSet中通过列索引获取数据,并将其转换为 Java 类型。 -
getResult(CallableStatement cs, int columnIndex):从CallableStatement中获取存储过程的输出参数,并将其转换为 Java 类型。
2. 示例:处理复杂 JSON 数据类型
假设你有一个数据库表,其中有一列存储的是 JSON 格式的字符串,但在 Java 中,你希望将它映射为一个
Map<String, Object>。Step 1: 创建一个自定义的
TypeHandler-
import com.fasterxml.jackson.core.type.TypeReference;
-
import com.fasterxml.jackson.databind.ObjectMapper;
-
import org.apache.ibatis.type.BaseTypeHandler;
-
import org.apache.ibatis.type.JdbcType;
-
-
import java.sql.*;
-
-
public class JsonTypeHandler extends BaseTypeHandler<Map<String, Object>> {
-
-
private static final ObjectMapper objectMapper = new ObjectMapper();
-
-
-
public void setNonNullParameter(PreparedStatement ps, int i, Map<String, Object> parameter, JdbcType jdbcType) throws SQLException {
-
ps.setString(i, toJson(parameter));
-
}
-
-
-
public Map<String, Object> getNullableResult(ResultSet rs, String columnName) throws SQLException {
-
return toMap(rs.getString(columnName));
-
}
-
-
-
public Map<String, Object> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-
return toMap(rs.getString(columnIndex));
-
}
-
-
-
public Map<String, Object> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
-
return toMap(cs.getString(columnIndex));
-
}
-
-
private String toJson(Map<String, Object> map) {
-
try {
-
return objectMapper.writeValueAsString(map);
-
} catch (Exception e) {
-
throw new RuntimeException("Error converting Map to JSON string", e);
-
}
-
}
-
-
private Map<String, Object> toMap(String json) {
-
try {
-
return objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {});
-
} catch (Exception e) {
-
throw new RuntimeException("Error converting JSON string to Map", e);
-
}
-
}
-
}
-
toJson():将Map<String, Object>转换为 JSON 字符串。 -
toMap():将 JSON 字符串转换为Map<String, Object>。
Step 2: 在 MyBatis 配置文件中注册
TypeHandler你可以在 MyBatis 的配置文件(
mybatis-config.xml)中注册自定义的TypeHandler:-
<typeHandlers>
-
<typeHandler handler="com.example.JsonTypeHandler" javaType="java.util.Map" jdbcType="VARCHAR"/>
-
</typeHandlers>
Step 3: 在 Mapper 中使用
TypeHandler在你的 Mapper 接口中,你可以直接使用这个
TypeHandler处理相关的字段:-
public interface UserMapper {
-
-
-
-
-
-
User selectUserById(int id);
-
}
在这个例子中,数据库中的
extra_info列是一个 JSON 格式的字符串,而在 Java 对象中,它被映射为Map<String, Object>类型。3. 将 TypeHandler 绑定到具体字段
你也可以在 MyBatis XML 映射文件中,将自定义的
TypeHandler绑定到具体的字段上:-
<resultMap id="UserResultMap" type="com.example.User">
-
<id property="id" column="id"/>
-
<result property="name" column="name"/>
-
<result property="extraInfo" column="extra_info" typeHandler="com.example.JsonTypeHandler"/>
-
</resultMap>
总结
-
TypeHandler的作用:TypeHandler是 MyBatis 用于在 Java 类型和 JDBC 类型之间进行转换的机制,处理复杂类型的转换时非常有用。 -
自定义
TypeHandler:通过实现TypeHandler接口,可以定制 MyBatis 如何将复杂类型与数据库字段进行映射。 -
使用场景:自定义
TypeHandler特别适用于处理复杂的数据类型,如 JSON、枚举、自定义对象等。
通过自定义
TypeHandler,你可以使 MyBatis 更加灵活地处理复杂的数据类型,满足特定应用场景下的需求。 -

浙公网安备 33010602011771号