diff --git a/pom.xml b/pom.xml
index 2f2fc2b34..974cb0737 100644
--- a/pom.xml
+++ b/pom.xml
@@ -141,6 +141,7 @@
weekend
generator
spring-boot-starter
+ solon-plugin
diff --git a/solon-plugin/pom.xml b/solon-plugin/pom.xml
new file mode 100644
index 000000000..832f263a8
--- /dev/null
+++ b/solon-plugin/pom.xml
@@ -0,0 +1,87 @@
+
+
+
+
+ 4.0.0
+
+ tk.mybatis
+ mapper-modules
+ ${revision}
+
+ mapper-solon-plugin
+ jar
+
+ mapper-solon-plugin
+ Solon Support for Mapper
+ https://github.com/abel533/solon-plugin/
+
+
+ 2.7.3
+
+
+
+
+
+ org.noear
+ mybatis-solon-plugin
+ ${solon.version}
+
+
+
+ tk.mybatis
+ mapper-core
+ ${project.version}
+
+
+
+ tk.mybatis
+ mapper-base
+ ${project.version}
+
+
+
+ org.noear
+ solon-test-junit4
+ ${solon.version}
+ test
+
+
+
+ com.h2database
+ h2
+ 1.4.200
+ test
+
+
+
+ com.zaxxer
+ HikariCP
+ 4.0.3
+ test
+
+
+
+
diff --git a/solon-plugin/src/main/java/tk/mybatis/solon/TkMapperAdapterFactory.java b/solon-plugin/src/main/java/tk/mybatis/solon/TkMapperAdapterFactory.java
new file mode 100644
index 000000000..e36cd0bf3
--- /dev/null
+++ b/solon-plugin/src/main/java/tk/mybatis/solon/TkMapperAdapterFactory.java
@@ -0,0 +1,24 @@
+package tk.mybatis.solon;
+
+import org.apache.ibatis.solon.MybatisAdapter;
+import org.apache.ibatis.solon.MybatisAdapterFactory;
+import org.noear.solon.core.BeanWrap;
+import org.noear.solon.core.Props;
+
+/**
+ * @title: tkMybatis Adapter Factory
+ * @author: trifolium.wang
+ * @date: 2024/4/1
+ * @since 2.7.3
+ */
+public class TkMapperAdapterFactory implements MybatisAdapterFactory {
+ @Override
+ public MybatisAdapter create(BeanWrap dsWrap) {
+ return new TkMapperMybatisAdapter(dsWrap);
+ }
+
+ @Override
+ public MybatisAdapter create(BeanWrap dsWrap, Props dsProps) {
+ return new TkMapperMybatisAdapter(dsWrap, dsProps);
+ }
+}
diff --git a/solon-plugin/src/main/java/tk/mybatis/solon/TkMapperMybatisAdapter.java b/solon-plugin/src/main/java/tk/mybatis/solon/TkMapperMybatisAdapter.java
new file mode 100644
index 000000000..fb92d69ff
--- /dev/null
+++ b/solon-plugin/src/main/java/tk/mybatis/solon/TkMapperMybatisAdapter.java
@@ -0,0 +1,112 @@
+package tk.mybatis.solon;
+
+import org.apache.ibatis.mapping.Environment;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.ibatis.solon.integration.MybatisAdapterDefault;
+import org.noear.solon.core.BeanWrap;
+import org.noear.solon.core.Props;
+import org.noear.solon.core.PropsConverter;
+import org.noear.solon.core.VarHolder;
+import tk.mybatis.mapper.entity.Config;
+import tk.mybatis.mapper.mapperhelper.MapperHelper;
+
+/**
+ * @title: TkMybatis Adapter
+ * @author: trifolium.wang
+ * @date: 2024/4/1
+ * @since 2.7.3
+ */
+public class TkMapperMybatisAdapter extends MybatisAdapterDefault {
+
+ protected Config tkConfig;
+
+ protected MapperHelper mapperHelper;
+
+ protected TkMapperMybatisAdapter(BeanWrap dsWrap) {
+ super(dsWrap);
+
+ dsWrap.context().getBeanAsync(Config.class, bean -> {
+ tkConfig = bean;
+ });
+
+ dsWrap.context().getBeanAsync(MapperHelper.class, bean -> {
+ mapperHelper = bean;
+ });
+ }
+
+ protected TkMapperMybatisAdapter(BeanWrap dsWrap, Props dsProps) {
+ super(dsWrap, dsProps);
+
+ dsWrap.context().getBeanAsync(Config.class, bean -> {
+ tkConfig = bean;
+ });
+
+ dsWrap.context().getBeanAsync(MapperHelper.class, bean -> {
+ mapperHelper = bean;
+ });
+ }
+
+ @Override
+ protected void initConfiguration(Environment environment) {
+ config = new tk.mybatis.mapper.session.Configuration();
+ config.setEnvironment(environment);
+
+ Props mybatisProps = dsProps.getProp("configuration");
+ if (!mybatisProps.isEmpty()) {
+ PropsConverter.global().convert(mybatisProps, config, Configuration.class, null);
+ }
+ }
+
+ @Override
+ public SqlSessionFactory getFactory() {
+ if (factory == null) {
+ builderMapperHelper();
+ factory = factoryBuilder.build(config);
+ }
+ return factory;
+ }
+
+ @Override
+ public void injectTo(VarHolder varH) {
+ super.injectTo(varH);
+
+ //@Db("db1") Config tkConfig;
+ if (Config.class.isAssignableFrom(varH.getType())) {
+ varH.setValue(this.tkConfig);
+ }
+
+ //@Db("db1") tk.mybatis.mapper.session.Configuration configuration;
+ if (tk.mybatis.mapper.session.Configuration.class.isAssignableFrom(varH.getType())) {
+ varH.setValue(getConfiguration());
+ }
+
+ //@Db("db1") MapperHelper mapperHelper;
+ if (MapperHelper.class.isAssignableFrom(varH.getType())) {
+ varH.setValue(this.mapperHelper);
+ }
+ }
+
+ /**
+ * 通过使用 tk.mybatis.mapper.session.Configuration
+ * 替换 MyBatis 中的 org.apache.ibatis.session.Configuration.
+ * 重写原 Configuration 中的 addMappedStatement实现
+ */
+ private void builderMapperHelper() {
+ Props cfgProps = dsProps.getProp("tk.mapper");
+
+ if (tkConfig == null) {
+ tkConfig = new Config();
+ }
+
+ if (!cfgProps.isEmpty()) {
+ PropsConverter.global().convert(cfgProps, tkConfig, Config.class, null);
+ }
+ if (mapperHelper == null) {
+ mapperHelper = new MapperHelper();
+ }
+
+ mapperHelper.setConfig(tkConfig);
+ ((tk.mybatis.mapper.session.Configuration) config).setMapperHelper(mapperHelper);
+ }
+}
diff --git a/solon-plugin/src/main/java/tk/mybatis/solon/XPluginImpl.java b/solon-plugin/src/main/java/tk/mybatis/solon/XPluginImpl.java
new file mode 100644
index 000000000..75ef5c2f7
--- /dev/null
+++ b/solon-plugin/src/main/java/tk/mybatis/solon/XPluginImpl.java
@@ -0,0 +1,22 @@
+package tk.mybatis.solon;
+
+import org.apache.ibatis.solon.integration.MybatisAdapterManager;
+import org.noear.solon.core.AppContext;
+import org.noear.solon.core.Plugin;
+
+/**
+ * @title: TkMybatis的Solon插件
+ * @author: trifolium.wang
+ * @date: 2024/4/1
+ * @since 2.7.3
+ */
+public class XPluginImpl implements Plugin {
+
+
+ @Override
+ public void start(AppContext context) throws Throwable {
+
+ MybatisAdapterManager.setAdapterFactory(new TkMapperAdapterFactory());
+ }
+
+}
diff --git a/solon-plugin/src/main/resources/META-INF/solon/mybatis-tkmapper-solon-plugin.properties b/solon-plugin/src/main/resources/META-INF/solon/mybatis-tkmapper-solon-plugin.properties
new file mode 100644
index 000000000..14e843dbd
--- /dev/null
+++ b/solon-plugin/src/main/resources/META-INF/solon/mybatis-tkmapper-solon-plugin.properties
@@ -0,0 +1,2 @@
+solon.plugin=tk.mybatis.solon.XPluginImpl
+solon.plugin.priority=3
\ No newline at end of file
diff --git a/solon-plugin/src/test/java/tk/mybatis/solon/test/TkMapperTest.java b/solon-plugin/src/test/java/tk/mybatis/solon/test/TkMapperTest.java
new file mode 100644
index 000000000..f73d3962b
--- /dev/null
+++ b/solon-plugin/src/test/java/tk/mybatis/solon/test/TkMapperTest.java
@@ -0,0 +1,15 @@
+package tk.mybatis.solon.test;
+
+import org.noear.solon.Solon;
+
+/**
+ * @title: TkMapperTest
+ * @author: trifolium.wang
+ * @date: 2024/4/2
+ */
+public class TkMapperTest {
+
+ public static void main(String[] args) {
+ Solon.start(TkMapperTest.class, args);
+ }
+}
diff --git a/solon-plugin/src/test/java/tk/mybatis/solon/test/conf/TestConfig.java b/solon-plugin/src/test/java/tk/mybatis/solon/test/conf/TestConfig.java
new file mode 100644
index 000000000..5fa149a1c
--- /dev/null
+++ b/solon-plugin/src/test/java/tk/mybatis/solon/test/conf/TestConfig.java
@@ -0,0 +1,48 @@
+package tk.mybatis.solon.test.conf;
+
+import com.zaxxer.hikari.HikariDataSource;
+import org.noear.solon.annotation.Bean;
+import org.noear.solon.annotation.Configuration;
+import org.noear.solon.annotation.Inject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.Statement;
+
+/**
+ * @title: TestConfig
+ * @author: trifolium.wang
+ * @date: 2024/4/2
+ */
+@Configuration
+public class TestConfig {
+
+ Logger log = LoggerFactory.getLogger(TestConfig.class);
+
+ @Bean(name = "db1", typed = true)
+ public DataSource db1(@Inject("${test.db1}") HikariDataSource ds) {
+ try {
+ Connection conn = ds.getConnection();
+ Statement statement = conn.createStatement();
+ statement.execute("CREATE TABLE user (" +
+ " `id` bigint NOT NULL AUTO_INCREMENT PRIMARY KEY," +
+ " `name` varchar(255) DEFAULT NULL," +
+ " `age` int DEFAULT NULL," +
+ " `create_time` datetime DEFAULT NULL," +
+ " `is_del` tinyint(1) DEFAULT NULL" +
+ ")");
+
+ statement.execute("INSERT INTO `user` (`id`, `name`, `age`, `create_time`, `is_del`) VALUES (1, '张三', 11, '2024-04-02 13:38:56', 0);\n" +
+ "INSERT INTO `user` (`id`, `name`, `age`, `create_time`, `is_del`) VALUES (2, '李四', 3, '2024-04-02 13:39:08', 0);\n" +
+ "INSERT INTO `user` (`id`, `name`, `age`, `create_time`, `is_del`) VALUES (3, '张麻子', 43, '2024-04-02 13:39:20', 0);");
+ statement.close();
+ conn.close();
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ throw new RuntimeException("Datasource initialization Failure!");
+ }
+ return ds;
+ }
+}
diff --git a/solon-plugin/src/test/java/tk/mybatis/solon/test/entity/User.java b/solon-plugin/src/test/java/tk/mybatis/solon/test/entity/User.java
new file mode 100644
index 000000000..900149620
--- /dev/null
+++ b/solon-plugin/src/test/java/tk/mybatis/solon/test/entity/User.java
@@ -0,0 +1,108 @@
+package tk.mybatis.solon.test.entity;
+
+import org.noear.snack.core.utils.DateUtil;
+import tk.mybatis.mapper.annotation.LogicDelete;
+
+import javax.persistence.Column;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.util.Date;
+
+@Table(name = "`user`")
+public class User {
+ @Id
+ @Column(name = "id")
+ @GeneratedValue(generator = "JDBC")
+ private Long id;
+
+ @Column(name = "`name`")
+ private String name;
+
+ @Column(name = "age")
+ private Integer age;
+
+ @Column(name = "create_time")
+ private Date createTime;
+
+ @LogicDelete
+ @Column(name = "is_del")
+ private Boolean isDel;
+
+ /**
+ * @return id
+ */
+ public Long getId() {
+ return id;
+ }
+
+ /**
+ * @param id
+ */
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ /**
+ * @return name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @param name
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return age
+ */
+ public Integer getAge() {
+ return age;
+ }
+
+ /**
+ * @param age
+ */
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ /**
+ * @return create_time
+ */
+ public Date getCreateTime() {
+ return createTime;
+ }
+
+ /**
+ * @param createTime
+ */
+ public void setCreateTime(Date createTime) {
+ this.createTime = createTime;
+ }
+
+ /**
+ * @return is_del
+ */
+ public Boolean getIsDel() {
+ return isDel;
+ }
+
+ /**
+ * @param isDel
+ */
+ public void setIsDel(Boolean isDel) {
+ this.isDel = isDel;
+ }
+
+ @Override
+ public String toString() {
+
+ return String.format("id:%d, name:%s, age:%d, createTime:%s", id, name, age,
+ createTime != null ? DateUtil.format(createTime, DateUtil.FORMAT_19_b) : null);
+ }
+}
\ No newline at end of file
diff --git a/solon-plugin/src/test/java/tk/mybatis/solon/test/mapper/UserMapper.java b/solon-plugin/src/test/java/tk/mybatis/solon/test/mapper/UserMapper.java
new file mode 100644
index 000000000..0dc60d2e6
--- /dev/null
+++ b/solon-plugin/src/test/java/tk/mybatis/solon/test/mapper/UserMapper.java
@@ -0,0 +1,18 @@
+package tk.mybatis.solon.test.mapper;
+
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.ResultMap;
+import org.apache.ibatis.annotations.Select;
+import tk.mybatis.mapper.common.Mapper;
+import tk.mybatis.solon.test.entity.User;
+
+import java.util.List;
+
+public interface UserMapper extends Mapper {
+
+ @ResultMap("tk.mybatis.solon.test.mapper.UserMapper.BaseResultMap")
+ @Select("SELECT * FROM user WHERE is_del = 0 AND age > #{age}")
+ List findByGTAge(@Param("age") Integer age);
+
+ List findByName(@Param("name") String name);
+}
\ No newline at end of file
diff --git a/solon-plugin/src/test/java/tk/mybatis/solon/test/service/TkMapperServiceTest.java b/solon-plugin/src/test/java/tk/mybatis/solon/test/service/TkMapperServiceTest.java
new file mode 100644
index 000000000..a0e2a85ff
--- /dev/null
+++ b/solon-plugin/src/test/java/tk/mybatis/solon/test/service/TkMapperServiceTest.java
@@ -0,0 +1,85 @@
+package tk.mybatis.solon.test.service;
+
+import org.apache.ibatis.solon.annotation.Db;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.noear.solon.test.SolonJUnit4ClassRunner;
+import org.noear.solon.test.SolonTest;
+import org.noear.solon.test.annotation.Rollback;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import tk.mybatis.mapper.entity.Example;
+import tk.mybatis.solon.test.TkMapperTest;
+import tk.mybatis.solon.test.entity.User;
+import tk.mybatis.solon.test.mapper.UserMapper;
+
+import java.util.List;
+
+/**
+ * @title: TkMapperServiceTest
+ * @author: trifolium.wang
+ * @date: 2024/4/2
+ */
+//@Ignore
+@SolonTest(TkMapperTest.class)
+@RunWith(SolonJUnit4ClassRunner.class)
+public class TkMapperServiceTest {
+
+ Logger log = LoggerFactory.getLogger(TkMapperServiceTest.class);
+
+ @Db("db1")
+ private UserMapper userMapper;
+
+ @Test
+ public void all() {
+
+ userMapper.selectAll().forEach(u -> log.info(u.toString()));
+ }
+
+ /**
+ * 根据主键查询
+ */
+ @Test
+ public void byId() {
+
+ User user = userMapper.selectByPrimaryKey(1);
+ log.info(user == null ? null : user.toString());
+ }
+
+ /**
+ * 根据example查询
+ */
+ @Test
+ public void exampleQuery() {
+ Example example = new Example(User.class);
+ example.and().andLike("name", "%张%");
+ userMapper.selectByExample(example).forEach(u -> log.info(u.toString()));
+ }
+
+ /**
+ * mybatis 原生查询
+ */
+ @Test
+ public void rawMybatisQuery() {
+
+ userMapper.findByGTAge(11).forEach(u -> log.info(u.toString()));
+ }
+
+ /**
+ * mybatis 逻辑删除和添加,并测试事务
+ */
+ @Test
+ @Rollback
+ public void logicDelInsert() {
+
+ List users = userMapper.findByName("张麻子");
+ if (!users.isEmpty()) {
+ User user = users.get(0);
+ userMapper.deleteByPrimaryKey(user.getId());
+ user.setId(null);
+ userMapper.insert(user);
+ }
+ }
+
+}
diff --git a/solon-plugin/src/test/resources/app.yml b/solon-plugin/src/test/resources/app.yml
new file mode 100644
index 000000000..a76e959f5
--- /dev/null
+++ b/solon-plugin/src/test/resources/app.yml
@@ -0,0 +1,21 @@
+# 配置数据源
+test:
+ db1:
+ jdbcUrl: jdbc:h2:mem:h2DB
+ driverClassName: org.h2.Driver
+ username: root
+ password: root
+
+mybatis:
+ db1:
+ typeAliases:
+ - "tk.mybatis.solon.test.entity.*"
+ mappers:
+ - "classpath:mapper/*.xml"
+
+# tk mapper的配置
+ tk:
+ mapper:
+ style: camelhumpandlowercase
+ safe-update: true
+ safe-delete: true
diff --git a/solon-plugin/src/test/resources/mapper/UserMapper.xml b/solon-plugin/src/test/resources/mapper/UserMapper.xml
new file mode 100644
index 000000000..3a279814c
--- /dev/null
+++ b/solon-plugin/src/test/resources/mapper/UserMapper.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id, `name`, age, create_time, is_del
+
+
+
+
\ No newline at end of file