一. 集成MybatisPlus
1.1 依赖添加
implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.3.1'
implementation 'com.baomidou:mybatis-plus-boot-starter-test:3.5.3.1'
implementation 'com.mysql:mysql-connector-j'
implementation 'com.alibaba:druid-spring-boot-3-starter:1.2.18'
// 只是在代码生成测试阶段用
testImplementation 'com.baomidou:mybatis-plus-generator:3.5.3.1'
implementation 'org.apache.velocity:velocity-engine-core:2.3'
1.2 配置
新建application.yml配置如下:
spring:
jackson:
# JSON 序列化不返回值为NULL的字段
default-property-inclusion: NON_EMPTY
datasource:
type: com.alibaba.druid.pool.DruidDataSource
# druid 连接池管理
druid:
filter:
config:
# 开启密钥加密
enabled: true
stat:
enabled: true
# 配置默认的监控统计拦截的Filter
# 不配置则监控页面中的SQL无法统计
# stat - SQL监控配置
# wall - SQL防火墙配置
# slf4j - Druid日志配置
filters: stat,wall,slf4j
# 初始化连接池大小
initial-size: 20
# 连接池最大连接数
max-active: 500
# 每个连接上PSCache的最大值
# 如果大于0,pool-prepared-statements自动开启
max-pool-prepared-statement-per-connection-size: -1
# 连接时最大等待时间(单位:毫秒)
max-wait: 60000
# 保持空闲连接不被关闭的最小生存时间(单位:毫秒)
min-evictable-idle-time-millis: 25200000
# 连接池最小空闲数
min-idle: 0
# 是否开启PSCache,即是否缓存preparedStatement(提升写入、查询效率)
# 建议在支持游标的数据库开启,例如:Oracle
pool-prepared-statements: false
# 检测获取连接时的有效性
# 开启后会影响性能
test-on-borrow: false
# 检测归还连接时的有效性
# 开启后会影响性能
test-on-return: false
# 检测空闲连接
# 不影响性能,建议开启
test-while-idle: true
# 检测关闭空闲连接的时间间隔(单位:毫秒)
time-between-eviction-runs-millis: 60000
# 检测连接有效的SQL
# 为空则test-while-idle、test-on-borrow、test-on-return配置失效
validation-query: SELECT 1
# 检测连接是否有效的超时时间
validation-query-timeout: 1
stat-view-servlet:
# 访问白名单
allow: 127.0.0.1
# 配置统计页面
enabled: true
# 访问密码
login-password: suanfaxiaosheng
# 访问用户名
login-username: root
# 允许重置监控数据
reset-enable: true
web-stat-filter:
# 配置统计页面过滤
enabled: true
# 排除路径
exclusions: .js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*
# 开启session统计
session-stat-enable: true
# session统计的最大个数
session-stat-max-count: 100
# 过滤路径
url-pattern: /*
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
global-config:
db-config:
# 主键类型:自增
id-type: auto
# mapper xml文件路径
mapper-locations: classpath:mapper/*.xml
在application-dev.yml配置错误打印SQL日志
spring:
datasource:
druid:
url: jdbc:mysql://127.0.0.1:3306/cloud?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&failOverReadOnly=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: cloud
password: 123456
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
1.3 新建user.sql脚本
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户ID',
`username` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '登录账号',
`name` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '姓名',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
1.4 新建代码生成工具类FastAutoGeneratorTest.java
/**
* 根据数据表生成代码工具
*
* @author shenjian
* @since 2023/8/16
*/
public class FastAutoGeneratorTest {
public static void main(String[] args) {
DataSourceConfig.Builder dataSourceConfig = new DataSourceConfig.Builder("jdbc:mysql://127.0.0.1:3306/cloud?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&failOverReadOnly=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai","cloud","123456");
FastAutoGenerator.create(dataSourceConfig)
// 全局配置
.globalConfig((scanner, builder) -> builder.enableSwagger().author(scanner.apply("请输入作者名称?")))
// 包配置
.packageConfig((scanner, builder) -> builder.parent(scanner.apply("请输入包名?")))
// 策略配置
.strategyConfig((scanner, builder) -> builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")))
.controllerBuilder().enableRestStyle().enableHyphenStyle()
.entityBuilder().enableLombok().enableTableFieldAnnotation().idType(IdType.INPUT).formatFileName("%sModel").build())
.execute();
}
// 处理 all 情况
protected static List<String> getTables(String tables) {
return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
}
}
运行main方法根据提示创建包即可, 生成代码后我们在根据需要修改即可
1.5 新建自动注入然后正常启动项目即可
resources目录下新建META-INF.spring目录,然后新建文件
org.springframework.boot.autoconfigure.AutoConfiguration.imports内容如下
com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure
至此,可以正常启动项目,集成MybatisPlus完毕,对于使用语法,可以去官网查询,后续我们将介绍特殊的一些用法
二. 批量插入
MybatisPlus自带的批量插入为伪批量插入,故此我们自定义批量插入方法
2.1 新建InsertBatchSqlInjector类
/**
* 自定义批量插入 SQL 注入器,Mybatis-Plus自带的saveBatch为伪批量插入
*/
public class InsertBatchSqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
// super.getMethodList() 保留 Mybatis Plus 自带的方法
List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
// 添加自定义方法:批量插入,方法名为 insertBatchSomeColumn
methodList.add(new InsertBatchSomeColumn());
return methodList;
}
}
2.2 新建MybatisPlusConfig类
@Configuration
@MapperScan("online.shenjian.cloud.**.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页用
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
/**
* 自定义批量插入 SQL 注入器
*/
@Bean
public InsertBatchSqlInjector insertBatchSqlInjector() {
return new InsertBatchSqlInjector();
}
}
2.3 新建MyBaseMapper接口
/**
* MyBaseMapper支持高效批量插入
*/
public interface MyBaseMapper<T> extends BaseMapper<T> {
/**
* 批量插入
*
* @param batchList
* @return
*/
int insertBatchSomeColumn(@Param("list") List<T> batchList);
}
2.4 修改UserMapper继承接口
@Repository
public interface UserMapper extends MyBaseMapper<User> {
}
2.5 新建测试类UserTest类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = CloudApplication.class)
public class UserTest {
@Autowired
private UserMapper userMapper;
/**
* 批量插入
*/
@Test
public void testBatchSave() {
List<User> users = new ArrayList<>();
User userOne = new User();
userOne.setId(IdUtil.getSnowflakeNextIdStr());
userOne.setName("沈健");
userOne.setUsername("shenjian");
User userTwo = new User();
userTwo.setId(IdUtil.getSnowflakeNextIdStr());
userTwo.setName("算法小生");
userTwo.setUsername("sfxs");
users.add(userOne);
users.add(userTwo);
userMapper.insertBatchSomeColumn(users);
}
}
OK, 批量插入成功
三. 分页查询
3.1 新建UserPlusMapper.java
/**
* 用户加强版Mapper
*/
@Repository
public interface UserPlusMapper extends BaseMapper<UserDto> {
/**
* 直接写SQL即可,即使关联查询的结果也会映射到DTO中
*/
String querySql = "SELECT " +
" u.username, u.name " +
" FROM " +
" user AS u";
String wrapperSql = "SELECT * FROM ( " + querySql + " ) AS q ${ew.customSqlSegment}";
@Select(wrapperSql)
Page<UserDto> page(IPage page, @Param("ew") Wrapper queryWrapper);
}
3.2 测试验证
/**
* @author shenjian
* @since 2023/10/12
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = CloudApplication.class)
public class UserTest {
@Autowired
private UserPlusMapper userPlusMapper;
/**
* 分页查询
*/
@Test
public void testPage() {
IPage<UserDto> page = new Page<>(1, 10);
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", "sfxs");
Page<UserDto> resultPage = userPlusMapper.page(page, queryWrapper);
Assert.assertNotNull(resultPage.getRecords());
Assert.assertEquals(1, resultPage.getTotal());
}
}
四. 定制化代码生成器类
往往遗留代码生成的类格式或者命名不符合要求,需要手工修改,但是当表很多时就比较头痛,所以我们自定义模板在进行代码生成
4.1 新建MyTemplateEngine.java类
里面大多实现直接拷贝自VelocityTemplateEngine.java, 只是增加了反射,修改包名,搜索修改地方即可找到两处位置
public class MyTemplateEngine extends AbstractTemplateEngine {
/**
* 批量输出 java xml 文件
*/
@NotNull
public AbstractTemplateEngine batchOutput() {
try {
ConfigBuilder config = this.getConfigBuilder();
List<TableInfo> tableInfoList = config.getTableInfoList();
tableInfoList.forEach(tableInfo -> {
Map<String, Object> objectMap = this.getObjectMap(config, tableInfo);
// 修改地方一:通过反射修改属性值,替换不需要的前缀
Class<? extends TableInfo> clazz = tableInfo.getClass();
try {
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(tableInfo, tableInfo.getName().replaceFirst("tb_", ""));
Field entityName = clazz.getDeclaredField("entityName");
entityName.setAccessible(true);
entityName.set(tableInfo, tableInfo.getEntityName().replaceFirst("Tb", ""));
Field mapperName = clazz.getDeclaredField("mapperName");
mapperName.setAccessible(true);
mapperName.set(tableInfo, tableInfo.getMapperName().replaceFirst("Tb", ""));
Field xmlName = clazz.getDeclaredField("xmlName");
xmlName.setAccessible(true);
xmlName.set(tableInfo, tableInfo.getXmlName().replaceFirst("Tb", ""));
Field serviceName = clazz.getDeclaredField("serviceName");
serviceName.setAccessible(true);
serviceName.set(tableInfo, tableInfo.getServiceName().replaceFirst("ITb", ""));
Field serviceImplName = clazz.getDeclaredField("serviceImplName");
serviceImplName.setAccessible(true);
serviceImplName.set(tableInfo, tableInfo.getServiceImplName().replaceFirst("Tb", ""));
Field controllerName = clazz.getDeclaredField("controllerName");
controllerName.setAccessible(true);
controllerName.set(tableInfo, tableInfo.getControllerName().replaceFirst("Tb", ""));
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
Optional.ofNullable(config.getInjectionConfig()).ifPresent(t -> {
// 添加自定义属性
t.beforeOutputFile(tableInfo, objectMap);
// 输出自定义文件
outputCustomFile(t.getCustomFiles(), tableInfo, objectMap);
});
// 修改地方二
objectMap.put("entity", String.valueOf(objectMap.get("entity")).replaceFirst("Tb", ""));
// entity
outputEntity(tableInfo, objectMap);
// mapper and xml
outputMapper(tableInfo, objectMap);
// service
outputService(tableInfo, objectMap);
// controller
outputController(tableInfo, objectMap);
});
} catch (Exception e) {
throw new RuntimeException("无法创建文件,请检查配置信息!", e);
}
return this;
}
private VelocityEngine velocityEngine;
{
try {
Class.forName("org.apache.velocity.util.DuckType");
} catch (ClassNotFoundException e) {
// velocity1.x的生成格式错乱 https://github.com/baomidou/generator/issues/5
LOGGER.warn("Velocity 1.x is outdated, please upgrade to 2.x or later.");
}
}
@Override
public @NotNull MyTemplateEngine init(@NotNull ConfigBuilder configBuilder) {
if (null == velocityEngine) {
Properties p = new Properties();
p.setProperty(ConstVal.VM_LOAD_PATH_KEY, ConstVal.VM_LOAD_PATH_VALUE);
p.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, StringPool.EMPTY);
p.setProperty(Velocity.ENCODING_DEFAULT, ConstVal.UTF8);
p.setProperty(Velocity.INPUT_ENCODING, ConstVal.UTF8);
p.setProperty("file.resource.loader.unicode", StringPool.TRUE);
velocityEngine = new VelocityEngine(p);
}
return this;
}
@Override
public void writer(@NotNull Map<String, Object> objectMap, @NotNull String templatePath, @NotNull File outputFile) throws Exception {
Template template = velocityEngine.getTemplate(templatePath, ConstVal.UTF8);
try (FileOutputStream fos = new FileOutputStream(outputFile);
OutputStreamWriter ow = new OutputStreamWriter(fos, ConstVal.UTF8);
BufferedWriter writer = new BufferedWriter(ow)) {
template.merge(new VelocityContext(objectMap), writer);
}
LOGGER.debug("模板:" + templatePath + "; 文件:" + outputFile);
}
@Override
public @NotNull String templateFilePath(@NotNull String filePath) {
final String dotVm = ".vm";
return filePath.endsWith(dotVm) ? filePath : filePath + dotVm;
}
}
4.2 模板文件替换修改
IDEA全局搜索需要的下图文件,拷贝至resources/templates下,然后把里面的内容根据自己需求进行模板调整
4.3 代码生成类
之前是Swagger注解,我们调整为Springdoc注解,并对实体不额外添加后缀%s
public class FastAutoGeneratorTest {
public static void main(String[] args) {
DataSourceConfig.Builder dataSourceConfig = new DataSourceConfig.Builder("jdbc:mysql://XXXX:3306/yy_dev?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&failOverReadOnly=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai","root","Gensci@123");
FastAutoGenerator.create(dataSourceConfig)
// 全局配置
.globalConfig((scanner, builder) -> builder.enableSpringdoc().author(scanner.apply("请输入作者名称?")))
// 包配置
.packageConfig((scanner, builder) -> builder.parent(scanner.apply("请输入包名?")))
// 策略配置
.templateEngine(new MyTemplateEngine())
.strategyConfig((scanner, builder) -> builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")))
.controllerBuilder().enableRestStyle().enableHyphenStyle()
.entityBuilder().enableLombok().enableTableFieldAnnotation().idType(IdType.INPUT).formatFileName("%s").build())
.execute();
}
// 处理 all 情况
protected static List<String> getTables(String tables) {
return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
}
}
4.4 效果如图
五、I18N实现国际化
我们实践项目中的国际化需求案例DEMO,相关可变内容在配置文件中进行编写,当然这只是对后端国际化改造,如果涉及到前端,自行改造即可
5.1 新建资源文件
在resources目录下新建目录i18n, 然后
新建messages_en.properties文件
user.login.error=account or password error!
新建messages_zh_CN.properties文件
user.login.error=帐户或密码错误!
5.2 新建LocaleConfig.java文件
当然可以作为可变配置,当前设置为默认中文Locale.CHINA
@Configuration
public class LocaleConfig {
@Bean
public ResourceBundleMessageSource messageSource() {
Locale.setDefault(Locale.CHINA);
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
//设置国际化文件存储路径和名称 i18n目录,messages文件名
source.setBasenames("i18n/messages", "i18n/error", "i18n/message-system");
//设置根据key如果没有获取到对应的文本信息,则返回key作为信息
source.setUseCodeAsDefaultMessage(true);
//设置字符编码
source.setDefaultEncoding("UTF-8");
return source;
}
}
5.3 新建Utils.java文件
实现ApplicationContextAware接口,获取对应的提示信息
@Component
public class Utils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
/**
* 获取国际化信息
*
* @param key
* String :传入的国际化key
* @param obj
* Object :传入的国际化参数
* @return String 返回国际化信息
*/
public static String getI18n(String key, Object[] obj) {
// 目前我在Utils中写死中文,具体使用时请可配置化
Locale locale = Locale.CHINA;
String menuName = applicationContext.getMessage(key, obj, "", locale);
return menuName;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (Utils.applicationContext == null) {
Utils.applicationContext = applicationContext;
}
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
}
5.4 改造登录接口
public ResponseVo login(UserDto userDto) {
// 判断是否存在该用户
if (user == null) {
return ResponseVo.error(Utils.getI18n("user.login.error", null));
}
}
5.5 启动项目验证
如果出现乱码情况,请将文件设置为UTF-8即可
欢迎关注公众号算法小生