diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5ff6309
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..21cfbda
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,73 @@
+
+
+ 4.0.0
+
+ org.example
+ mybaits-issue-2956
+ 1.0-SNAPSHOT
+
+
+ 8
+ 8
+ UTF-8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+ 2.7.15
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ 2.7.15
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+ 2.7.15
+
+
+
+
+
+
+
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+ 2.3.0
+
+
+
+
+ mysql
+ mysql-connector-java
+ 8.0.33
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.26
+ provided
+
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+
\ No newline at end of file
diff --git a/src/main/java/org/example/Application.java b/src/main/java/org/example/Application.java
new file mode 100644
index 0000000..1c6f53c
--- /dev/null
+++ b/src/main/java/org/example/Application.java
@@ -0,0 +1,18 @@
+package org.example;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+@EnableTransactionManagement
+@EnableAspectJAutoProxy
+@MapperScan(basePackages = "org.example.mapper")
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/example/entity/User.java b/src/main/java/org/example/entity/User.java
new file mode 100644
index 0000000..01ca786
--- /dev/null
+++ b/src/main/java/org/example/entity/User.java
@@ -0,0 +1,13 @@
+package org.example.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.example.enums.GcType;
+
+@Getter
+@Setter
+public class User {
+ private Long id;
+ private String uu;
+ private GcType type;
+}
diff --git a/src/main/java/org/example/enums/GcType.java b/src/main/java/org/example/enums/GcType.java
new file mode 100644
index 0000000..a73017c
--- /dev/null
+++ b/src/main/java/org/example/enums/GcType.java
@@ -0,0 +1,32 @@
+package org.example.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum GcType {
+ G1(1),
+ ZGC(2) {
+ @Override
+ public String getName() {
+ return "zgc";
+ }
+ },
+ ;
+
+ public String getName() {
+ return this.name();
+ }
+
+ private int value;
+
+ public static GcType fromValue(int i) {
+ for (GcType t : values()) {
+ if (t.value == i) {
+ return t;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/org/example/mapper/UserMapper.java b/src/main/java/org/example/mapper/UserMapper.java
new file mode 100644
index 0000000..227ee2f
--- /dev/null
+++ b/src/main/java/org/example/mapper/UserMapper.java
@@ -0,0 +1,28 @@
+package org.example.mapper;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Options;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.example.entity.User;
+import org.example.enums.GcType;
+
+@Mapper
+public interface UserMapper {
+ @Options(useGeneratedKeys = true, keyProperty = "id")
+ @Insert("insert into user (uu, `type`) values (#{user.uu}, #{user.type})")
+ int insert(@Param("user") User user);
+
+ @Select("select * from user where `type`=#{req.gcType}")
+ User selectByRequestParam(@Param("req") RequestParam req);
+
+
+ @Getter
+ @Setter
+ public static class RequestParam {
+ private GcType gcType;
+ }
+}
diff --git a/src/main/java/org/example/typehandler/CustomEnumTypeHandler.java b/src/main/java/org/example/typehandler/CustomEnumTypeHandler.java
new file mode 100644
index 0000000..b717bb9
--- /dev/null
+++ b/src/main/java/org/example/typehandler/CustomEnumTypeHandler.java
@@ -0,0 +1,45 @@
+package org.example.typehandler;
+
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedTypes;
+import org.example.enums.GcType;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@MappedTypes(GcType.class)
+public class CustomEnumTypeHandler extends BaseTypeHandler {
+ private Class type;
+
+ public CustomEnumTypeHandler() {}
+
+ public CustomEnumTypeHandler(Class type) {
+ if (type == null) {
+ throw new IllegalArgumentException("Type argument cannot be null");
+ }
+ this.type = type;
+ }
+
+ @Override
+ public void setNonNullParameter(PreparedStatement ps, int i, GcType parameter, JdbcType jdbcType) throws SQLException {
+ ps.setObject(i, parameter.getValue());
+ }
+
+ @Override
+ public GcType getNullableResult(ResultSet rs, String columnName) throws SQLException {
+ return GcType.fromValue(rs.getInt(columnName));
+ }
+
+ @Override
+ public GcType getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+ return GcType.fromValue(rs.getInt(columnIndex));
+ }
+
+ @Override
+ public GcType getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+ return GcType.fromValue(cs.getInt(columnIndex));
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..ef064f2
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,14 @@
+spring:
+ datasource:
+ url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ hikari:
+ jdbc-url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8
+ password:
+ username:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+
+mybatis:
+ type-handlers-package: org.example.typehandler
+# configuration:
+# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
\ No newline at end of file
diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql
new file mode 100644
index 0000000..7eed309
--- /dev/null
+++ b/src/main/resources/schema.sql
@@ -0,0 +1,9 @@
+
+CREATE TABLE `user`
+(
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `uu` varchar(255) NOT NULL,
+ `type` int DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4;
\ No newline at end of file
diff --git a/src/test/java/com/test/MybatisTest.java b/src/test/java/com/test/MybatisTest.java
new file mode 100644
index 0000000..707db34
--- /dev/null
+++ b/src/test/java/com/test/MybatisTest.java
@@ -0,0 +1,59 @@
+package com.test;
+
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.ibatis.type.EnumTypeHandler;
+import org.apache.ibatis.type.TypeHandler;
+import org.apache.ibatis.type.TypeHandlerRegistry;
+import org.example.Application;
+import org.example.entity.User;
+import org.example.enums.GcType;
+import org.example.mapper.UserMapper;
+import org.example.mapper.UserMapper.RequestParam;
+import org.example.typehandler.CustomEnumTypeHandler;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.transaction.annotation.Transactional;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = Application.class)
+@Transactional
+public class MybatisTest {
+ @Autowired
+ private SqlSessionFactory sqlSessionFactory;
+ @Autowired
+ private UserMapper userMapper;
+
+ @Test
+ public void testEnumTypeHandler() {
+ User user = new User();
+ user.setUu("user1");
+ user.setType(GcType.G1);
+
+ // insert into user (uu, `type`) values ('user1', '1');
+ userMapper.insert(user);
+
+ TypeHandlerRegistry typeHandlerRegistry = sqlSessionFactory.getConfiguration().getTypeHandlerRegistry();
+
+ TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(GcType.class);
+
+ // here
+ assertEquals(typeHandler.getClass(), CustomEnumTypeHandler.class);
+
+ RequestParam req = new RequestParam();
+ req.setGcType(GcType.ZGC);
+
+ // actual: select * from user where `type` = 'ZGC';
+ // expected: select * from user where `type` = 2;
+ userMapper.selectByRequestParam(req);
+
+ TypeHandler typeHandler2 = typeHandlerRegistry.getTypeHandler(GcType.class);
+
+ // here
+ assertEquals(typeHandler2.getClass(), EnumTypeHandler.class);
+ }
+}