mybatis 使用指南(全)
maven引入generator插件关于mybatis 的generator 的代码自动生成,官网为我们提供了多种整合方式,具体使用那个整合方式,视我们具体情况决定,这里我们采用 maven插件的形式,关于插件配置属性的参数,可以点进maven 连接中查看具体参数配置。说明参考官网说明。<plugin><groupId>org.mybatis.generator</g
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 遇到到问题
- 使用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);
}
}
- 如果我们使用生成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>
-
讲一点经验之谈
关于 MapperScan 和 mapper xml 配置文件大家老是拿不定配置是否正确,那我来告诉大家一个常事,
其实这两部分是分开的,我们只要确保,mapper xml 通过配置文件加载进了config,然后使用MapperScan注解扫描了所有的interface(其实就是一个session的代理)mybatis 源码总结
然后再确定两者的命名空间没问题,就绝逼他喵没问题。 -
因为引入了覆盖xml 文件的问题,所以我们一定要把手写的代码领出来单写(如果不解决覆盖,就会有追加重复代码的问题)
具体的应用案例,我放到了这个博客上面,大家可以参考一下。
mybatis总结 -
为了注释我们自己实现了一个简单的注释给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;
}
}
更多推荐


所有评论(0)