mybatis 使用指南(全)

关于 mybatis 的generator 的代码自动生成,官网为我们提供了多种整合方式,具体使用那个整合方式,视我们具体情况决定,这里
我们采用 maven插件的形式,关于插件配置属性的参数,可以点进maven 连接中查看具体参数配置。说明参考官网说明。

   <plugin>
      <groupId>org.mybatis.generator</groupId>
      <artifactId>mybatis-generator-maven-plugin</artifactId>
      <version>1.4.0</version>
      <configuration>
          <configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
          <overwrite>true</overwrite>
      </configuration>
      <executions>
          <execution>
              <id>Generate MyBatis Artifacts</id>
              <goals>
                  <goal>generate</goal>
              </goals>
          </execution>
      </executions>

      <dependencies>
          <!-- 为插件单独配置mysql驱动 -->
          <dependency>
              <groupId>mysql</groupId>
              <artifactId>mysql-connector-java</artifactId>
              <version>5.1.39</version>
          </dependency>
      </dependencies>
  </plugin>

配置中我们制定了代码生成器的具体配置,所以该插件会根据我么的配置文件进行代码生成。

关于generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
   <!-- <classPathEntry location="/Program Files/IBM/SQLLIB/java/db2java.zip" />-->
    <properties resource="generatorConfig.properties"/>

    <context id="DB2Tables" targetRuntime="MyBatis3">
        <!-- 插件 -->
        <plugin type="org.mybatis.generator.plugins.SerializablePlugin"/>
        <!--用来解决xml 文件覆盖的问题-->
        <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin">
            <property name="isMergeable" value="false"/>
            <property name="forceOverwrite" value="true"/>
        </plugin>


        <!-- 注释 -->
        <commentGenerator type="com.learn.pay.common.generator.mybatis.simple.SimpleCommentGenerator">
            <property name="javaFileEncoding" value="UTF-8"/>
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <jdbcConnection driverClass="${spring.datasource.driver-class-name}"
                        connectionURL="${spring.datasource.url}"
                        userId="${spring.datasource.username}"
                        password="${spring.datasource.password}">
            <property name="useInformationSchema" value="true"/>
        </jdbcConnection>

        <javaTypeResolver >
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <javaModelGenerator targetPackage="${generated.domain.dir.targetPackage}" targetProject="${generated.domain.dir}\src\main\java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
            <property name="rootClass" value="com.learn.pay.common.lang.BaseEntity"/>
        </javaModelGenerator>

        <sqlMapGenerator targetPackage="${generated.dao.dir.targetPackage}"  targetProject="${generated.mapper.dir}\src\main\resources">
        </sqlMapGenerator>

        <javaClientGenerator type="XMLMAPPER" targetPackage="${generated.dao.dir.targetPackage}"  targetProject="${generated.dao.dir}\src\main\java">
        </javaClientGenerator>

        <table tableName="user" domainObjectName="User" >
        </table>
        <table tableName="sys_menu" domainObjectName="SysMenu" >
            <generatedKey column="menu_id" sqlStatement="MySql" identity="true" />
        </table>
    </context>
</generatorConfiguration>

我们已经将具体的配置抽出为配置文件了,可以灵活配置文件路径等相关参数。

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://101.34.136.47:3306/renren?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
spring.datasource.username=root
spring.datasource.password=123456

generated.domain.dir.targetPackage=com.example.springcloudstreamtest.domain
generated.mapper.dir.targetPackage=mappers
generated.dao.dir.targetPackage=com.example.springcloudstreamtest.dao
# 切记 一定是\\ 转义
generated.domain.dir=D:\\code\\springcloudstreamtest
generated.mapper.dir=D:\\code\\springcloudstreamtest
generated.dao.dir=D:\\code\\springcloudstreamtest

关于targetRuntime 的不同,官网已经为我们详细介绍了,参考如下

其他配置

参考

spring boot 使用mybatis

@SpringBootApplication
//扫描该包
@MapperScan(basePackages={"com.example.springcloudstreamtest.dao"})
public class SpringcloudstreamtestApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudstreamtestApplication.class, args);
    }

}
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://101.34.136.47:3306/renren?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
spring.datasource.username=root
spring.datasource.password=123456
mybatis.mapper-locations=classpath*:mappers/*Mapper.xml

关于springboot xml文件存储在 非resources中打包不进去问题

<resources>
    <resource>
        <directory>src/main/java</directory>
        <includes>
            <include>**/*.xml</include>
        </includes>
    </resource>
    <resource>
        <directory>src/main/resources</directory>
        <includes>
            <include>**/*.xml</include>
            <include>**/*</include>
        </includes>
        <filtering>true</filtering>
    </resource>
</resources>

mybatis generator 遇到到问题

  1. 使用mybatis generator 插件方式的话有一定的局限性,比如,我们我们在配置文件中使用了自定义的SimpleCommentGenerator来生成代码注解
    并在配置文件中指定了,接下来就会出现如下问题.

导致改异常的原因也很简单,就是插件相当于是一个java 程序,那么她也有自己的classpath
但是我们配置文件中指定的是我们当前项目中的classpath 下的SimpleCommentGenerator,所以会找不到,解决方案有两种,一种是提供一个jar 就跟mysql一样添加进依赖中
另一中是通过配置文件将指定文件夹加载进classpath中,但是文件中的SimpleCommentGenerator一定要是编译后的。所以局限性啊

	<classPathEntry location="C:/Program Files/MySQL/mysql-connector-java-8.0.25.jar" />

考虑到这种局限性,我们还是老老实实的用代码写吧!!

public class GeneratorTest {

    public static void main(String[] args) throws Exception {
        InputStream configFile = GeneratorTest.class.getResourceAsStream("/generatorConfig.xml");
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
    }
}
  1. 如果我们使用生成mybatis mapper xml的话,会发现一个问题,就是无论我们如何设置想要进行文件的覆盖操作,都不会生效,比如
<context id="context1">
  <!-- ... 其他配置 ... -->
  <property name="overwrite" value="true"/>
</context>
        boolean overwrite = true;
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);

种种尝试都不会生效文件的覆盖,还是会追加,为啥类,找了半天源码才发现是代码里面写死的,咱就话说,这不是bug吗

private void writeGeneratedXmlFile(GeneratedXmlFile gxf, ProgressCallback callback)
            throws InterruptedException, IOException {
            File directory = shellCallback.getDirectory(gxf
                    .getTargetProject(), gxf.getTargetPackage());
            targetFile = new File(directory, gxf.getFileName());
            if (targetFile.exists()) {
                //永远都会进入到这里面,原因是 这个is mergeable 是他喵写死的
                if (gxf.isMergeable()) {
                    source = XmlFileMergerJaxp.getMergedSource(gxf,
                            targetFile);
                    //这个方法根本就来不及执行
                } else if (shellCallback.isOverwriteEnabled()) {
                }
            }
    }
    
      public List<GeneratedXmlFile> getGeneratedXmlFiles() {
    
            if (xmlMapperGenerator != null) {
                //兄弟们,看见了吗,true 这不是bug吗
               
                GeneratedXmlFile gxf = new GeneratedXmlFile(document,
                        getMyBatis3XmlMapperFileName(), getMyBatis3XmlMapperPackage(),
                        context.getSqlMapGeneratorConfiguration().getTargetProject(),
                        true, context.getXmlFormatter());
            }
    
            return answer;
        }
    

解决方案就是使用 plugin 进行复写,不过现在有现成的plugin 了

<plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin">
  <property name="isMergeable" value="false"/>
  <property name="forceOverwrite" value="true"/>
</plugin>
  1. 讲一点经验之谈
    关于 MapperScan 和 mapper xml 配置文件大家老是拿不定配置是否正确,那我来告诉大家一个常事,
    其实这两部分是分开的,我们只要确保,mapper xml 通过配置文件加载进了config,然后使用MapperScan注解扫描了所有的interface(其实就是一个session的代理)mybatis 源码总结
    然后再确定两者的命名空间没问题,就绝逼他喵没问题。

  2. 因为引入了覆盖xml 文件的问题,所以我们一定要把手写的代码领出来单写(如果不解决覆盖,就会有追加重复代码的问题)
    具体的应用案例,我放到了这个博客上面,大家可以参考一下。
    mybatis总结

  3. 为了注释我们自己实现了一个简单的注释给mybatis generator使用

public class SimpleCommentGenerator extends DefaultCommentGenerator {

    /**
     * 生成模型注释
     *
     * @param topLevelClass     类
     * @param introspectedTable 表
     */
    public void addModelClassComment(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        String remarks = introspectedTable.getRemarks();
        remarks = isEmpty(remarks) ? "" : remarks + ":";
        remarks = remarks.replace("\n", " ");

        topLevelClass.addJavaDocLine("/**");
        topLevelClass.addJavaDocLine(" * " + remarks + introspectedTable.getFullyQualifiedTable());
        topLevelClass.addJavaDocLine(" */");
    }

    /**
     * 生成字段注释
     *
     * @param field              字段
     * @param introspectedTable  表
     * @param introspectedColumn 表列
     */
    @Override
    public void addFieldComment(Field field, IntrospectedTable introspectedTable, IntrospectedColumn introspectedColumn) {
        String remarks = introspectedColumn.getRemarks();
        remarks = isEmpty(remarks) ? "" : remarks + ":";
        remarks = remarks.replace("\n", " ");

        field.addJavaDocLine("/**");
        field.addJavaDocLine(" * " + remarks + introspectedColumn.getActualColumnName());
        field.addJavaDocLine(" */");
    }

    /**
     * 是否为空
     *
     * @param value
     * @return
     */
    private boolean isEmpty(String value) {
        if (value != null && value.length() > 0) {
            return false;
        }
        return true;
    }

}

mybatis 中定义的类型转化器注册进 mybatis中

其实代码上 注不注册进去都行,不注册进入,mybatis 就会通过 newInstan 的方式反射创建实例对象,也就是每次执行sql语句的时候都会创建,效率太低,所有我们还是预先注册进入,之后直接获取就行了。关于反射类的泛型变量的代码就不展示了,大家可以上chatgpt上让他给你生成一份。

@Configuration
public class DaoConfiguration {
    @Autowired
    private DataSource dataSource;

    @Bean
    public SqlSessionFactory sqlSessionFactory() {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        try {
            List<String> packages = Lists.newArrayList();
            packages.add("com.learn.pay.domain.entity.base.type");
            List<String> handlerPackages = Lists.newArrayList("com.learn.pay.common.mybatis.typehandler");
            List<TypeHandler> typeHandlers  = TypeHandlerUtils.getTypeHandlerByPackages(handlerPackages,packages);
            bean.setTypeHandlers(typeHandlers.toArray(new TypeHandler[typeHandlers.size()]));
            return bean.getObject();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }
}

public class TypeHandlerUtils {
    /**
     * 扫描包下的所有 实现了 @link{Enumerable}的枚举 返回TypeHandler 返回TypeHandler包含基础包的一些 TypeHandler
     *
     * @param typePackage
     * @return
     */
    public static List<TypeHandler> getTypeHandlerByPackages(List<String> handlerPackage,List<String> typePackage) {
        TypeHandlerRegistry registry = new TypeHandlerRegistry();
        List<TypeHandler> typeHandlers = new ArrayList<>();
        // 公共 typeHandler
        List<Class> clazzList = new ArrayList<>();
        for (String aPackage : handlerPackage) {
            //过滤
            List<Class<?>> tmp = ClassUtils.getClasses(aPackage);
            for (Class<?> current : tmp) {
                if(ClassUtils.isConcrete(current) &&  current.getAnnotation(Exclude.class) == null) {
                    clazzList.add(current);
                }
            }

        }
        for (Class aClass : clazzList) {
            Type genericTypes = ClassUtils.getGenericTypes(aClass);
            if(genericTypes instanceof Class) {
                typeHandlers.add(
                        convertRawType(
                                registry.getInstance((Class<?>) genericTypes, aClass), (Class<?>) genericTypes));
            }
        }

        typePackage.stream()
                .forEach(
                        pack -> {
                            List<Class<?>> classes = ClassUtils.getClasses(pack);
                            classes.stream()
                                    .forEach(
                                            clazz -> {
//                                                LoggerUtils.info(CommonLogger.BIZ, "init type handler class:", clazz.getName());
                                                if (null != clazz.getEnumConstants()) {
                                                    boolean match =
                                                            Arrays.stream(clazz.getEnumConstants())
                                                                    .allMatch(e -> (e instanceof Enumerable));
                                                    //匹配上并且枚举实例大于0
                                                    if (match && clazz.getEnumConstants().length > 0) {
                                                        typeHandlers.add(
                                                                convertRawType(
                                                                        registry.getInstance(clazz, TypeCodeTypeHandler.class), clazz));
                                                    }
                                                }
                                            });
                        });
        return typeHandlers;
    }

    /**
     * 将自定义 typeHandler内rawType转换为真正的类型
     *
     * @param typeHandler
     * @param rawType
     * @return
     */
    private static TypeHandler convertRawType(TypeHandler typeHandler, Class rawType) {
        TypeReference typeReference = (TypeReference) typeHandler;
        try {
            Field field = TypeReference.class.getDeclaredField("rawType");
            field.setAccessible(true);
            field.set(typeReference, rawType);
        } catch (Exception e) {
            LoggerUtils.error(
                    CommonLogger.ERROR, e.getMessage(), typeHandler.getClass(), "转换rawType失败", e);
        }
        return (TypeHandler) typeReference;
    }
}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐