1、Mybatis缺点
相信来看博客后端大佬们都用过mybatis或者它的plus版吧,如果没有那Hibernate、JPA至少有一款绝对是你常用的数据库持久层框架,不知道你是否会感觉到每次使用mybatis时,会感到有一些比较繁杂冗余的地方,比如建了一个新表,得随之加一个新表对应的映射吧,即使仅仅给表加了一个字段,你也得去对应的映射去加上这个字段,而且建映射的时候还有很多注意事项,比如标记主键啊:如果你的主键不是id但是没有给字段上加上@TableId注释,你就会发现新增的数据主键没了,就问头不头大?来看看百度到的这些个缺点
- SQL 映射复杂:虽然 MyBatis 可以让开发人员更加方便地编写 SQL语句,但是这同时也意味着需要开发者自己解决一些查询语句上的问题。在相当多数情况下,使用 MyBatis 需要具备一定的 SQL能力才可以完成项目的 CRUD 操作,否则可能会导致代码质量差、映射不正确等问题。
- XML 配置维护困难:MyBatis 的配置文件需要通过 XML 来进行书写,这种方式与 Java代码并不是很直观。由于项目规模越来越大,配置文件中的内容也逐渐增多,这就导致了配置文件变得十分庞大、难以维护。
- 编码效率较低:MyBatis 对于开发人员的编码技术要求比较高,因为需要手动编写 SQL查询语句和参数映射。这就增加了开发人员的工作量,降低了开发效率,对技术水平要求也相应提高。
- 难以掌控SQL执行细节: 在某些场景下,特别是对于更加复杂的 SQL 执行,MyBatis 可能会顾及不到底层细节问题,导致性能劣化。需要开发者自行掌握数据库相关知识并评估。
- 重复代码:使用 MyBatis 进行开发时,需要为每个映射文件编写单独的 mapper 文件,这可能会导致出现大量冗余代码。
- 缺乏完备的文档和社区: 尽管 MyBatis 是一个流行的 ORM 框架,但是与其他一些框架相比,Mybatis 的中文资料较少且不清晰,同时社区活跃度较低。
作为一个混迹后端的三年小开发,我个人是觉得说的有道理的,上面说到的确实有几个痛点,但是我觉得还不是很全,让我补上几点:
- 如果要做比较复杂的表联接,用自带的方法写起来比较麻烦,不如直接写自定义查询;
- 写一个自定义查询也比较麻烦,需要在mapper里新建一个方法,新建完后还要在对应的xml写,sql写在xml中,麻烦!
- 如果查出来的字段是多个表中的某些字段或者有as后的自定义字段名,这样子就不能用表的映射实体类了,只能使用Map或者单独给这个数据结构建一个实体类,Map中的方法比较少,get()的结果是Object,用起来每次都需要转类型且容易报错
2、Jfinal的activeRecord介绍
相信我,这玩意你用了就知道有多爽!
独创的Db + Record模式太好用了真的,不用再纠结自建映射了,有自动生成映射的工具类,Record这个结果收集类也提供了很多get方法,特殊的类型直接getStr、getDate…等等,不再需要手动转类型;甚至如果你擅长写SQL的话,可以说完全用不着映射,配合Enjoy SQL 模板,写自定义SQL直接爽到起飞,增删改查一步到位,我想当一个个简简单单的CRUD员 (●’◡’●) 。
具体介绍可以看Jfinal的官方文档: ActiveRecord 5.1 概述
下面开始正文,介绍SpringBoot整合Jfinal的ActiveRecord的具体步骤
3、SpringBoot整合Jfinal的ActiveRecord
① pom引入
SpringBoot 的核心引入
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
</dependencies>
用到的工具类引入
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.3</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
整合JFinal activeRecord的核心引入
<!-- activeRecord引入-->
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>activerecord</artifactId>
<version>4.9.06</version>
</dependency>
<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.20</version>
</dependency>
<!-- Mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
build打包工具的引入(这个也不能忽视,不然后面会遇到问题)
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<!-- 这个一定不能少 少了打包后不会把sql文件打入 -->
<include>**/*.sql</include>
<include>**/*.txt</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
② 新建配置文件application.yml
server:
port: 11188
spring:
datasource:
url: jdbc:mysql://localhost:3306/project?zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useOldAliasMetadataBehavior=true&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
(仅供参考,具体的配置根据用者自己情况改)
③ 编写配置类config
@Configuration
public class ActiveRecordPluginConfig {
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
/**
* logger
*/
private static final Logger log = LoggerFactory.getLogger(ActiveRecordPluginConfig.class);
public DruidPlugin createDruidPlugin() {
return new DruidPlugin(url, username, password,driverClassName);
}
@Bean
public ActiveRecordPlugin initActiveRecordPlugin() {
// 配置druid数据库连接池插件
DruidPlugin druidPlugin = this.createDruidPlugin();
// 配置ActiveRecord插件
ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin);
arp.setShowSql(true);
arp.setDevMode(true);
log.info("自动初始化");
// 所有映射在 MappingKit 中自动化搞定
//_MappingKit.mapping(arp);
// com.active 填你的包名 这个工具类下一步会讲
AutoSqlMappingKit.auto(arp,new String[]{"com.active"});
// 开启插件
druidPlugin.start();
arp.start();
log.info("整合ActiveRecord成功");
return arp;
}
}
上面内容需要改AutoSqlMappingKit.auto(arp,new String[]{“com.active”});里的内容,内容是你的包名
④ 加载sql文件工具类AutoSqlMappingKit(重点)
public class AutoSqlMappingKit {
private static final Logger log = LoggerFactory.getLogger(AutoSqlMappingKit.class);
/**
* 默认路径是项目的相对路径
*/
public static void auto(ActiveRecordPlugin ds) {
if (isWinOS()) {
auto(ds, "/");
} else {
//获取项目跟路径
String webRootPath = AutoSqlMappingKit.class.getResource("/").getPath();
//获取项目绝对根路径
Path absoluteWebPath = Paths.get(webRootPath, "/");
auto(ds, absoluteWebPath.toString());
}
}
public static void auto(ActiveRecordPlugin arp, String... packgeName) {
auto(arp, CollectionUtil.newHashSet(packgeName));
}
/**
* 扫描jar包中的sql文件
*
* @param arp
*/
public static void auto(ActiveRecordPlugin arp, Set<String> packageName) {
debug("开始映射包路径{} sql文件", packageName.toString());
ScanKit.search(packageName, o -> true, new Medium<String, ISource>() {
@Override
public boolean test(String s) {
return StringKit.endsWithIgnoreCase(s, ".sql");
}
@Override
public ISource map(String s) {
try {
debug("映射sql文件:" + s);
if (s.startsWith(File.separator) || s.contains(":")) {
return new FileSource(null, s, Charset.defaultCharset().toString());
}
StringSource source = new StringSource(IoUtil.read(new InputStreamReader(this.getClass().getResourceAsStream("/" + s), "UTF-8")), true);
if (ReUtil.isMatch(".*#namespace.*#end.*", source.getContent().toString())) {
return source;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public void apply(ISource stringSource) {
if (stringSource != null) {
arp.addSqlTemplate(stringSource);
}
}
});
}
private static void debug(String msg, Object... args) {
log.debug(msg, args);
}
public static boolean isWinOS() {
String os = System.getProperty("os.name");
if (os.toLowerCase().startsWith("win")) {
return true;
} else {
return false;
}
}
}
这个工具类需要着重介绍一下,主要功能是把你写的sql模板读取出来,然后加载到③中的配置,其实就是整合了Enjoy SQL 模板,而此工具类的作用是让其自动化识别你的模板而无需一个一个配置
ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin);
// 将sql模板加载到ActiveRecordPlugin中
arp.addSqlTemplate("all.sql");
_MappingKit.mapping(arp);
me.add(arp);
如上所示这是官方提供的案例,当有多个sql模板时需要一个一个手动配置,比较麻烦;用AutoSqlMappingKit可以将包里的所有sql模板全部添加到ActiveRecordPlugin中
// com.active 填你的包名
AutoSqlMappingKit.auto(arp,new String[]{"com.active"});
AutoSqlMappingKit里边还使用了其他的工具类,其他的工具类代码比较多就不再粘贴到文章了,需要的可以下载文章绑定的资源,或者私信我自取(认准优快云:汤圆不扁),要是被转载了就找不到咯!
⑤ 启动项目
2024-01-18 19:43:42.851 [main] INFO com.jfinal.config.ActiveRecordPluginConfig - 自动初始化
2024-01-18 19:43:42.889 [main] DEBUG com.jfinal.kit.AutoSqlMappingKit - 开始映射包路径[com.active] sql文件
2024-01-18 19:43:42.901 [main] DEBUG com.jfinal.kit.AutoSqlMappingKit - 映射sql文件:D:\java_a\project\zode\active\target\classes\com\active\api\sql\demo.sql
2024-01-18 19:43:43.146 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
2024-01-18 19:43:43.249 [main] INFO com.jfinal.config.ActiveRecordPluginConfig - 整合ActiveRecord成功
当日志出现这些内容时,已经代表你成功整合好了,就是这么简单!
⑥ 尝试使用
增
// Db自带的方法Db.save 插入某条数据
Record savePara = new Record().set("id",1000).set("title","Db自带的方法Db.save新增的内容");
boolean save = Db.save("news", "id", savePara);
System.out.println("save = " + save);
// 通过使用SQL模板 插入某条数据
int b = Db.update(Db.getSqlPara(SQL_KEY + "alertNews", Kv.by("id", 1001).set("title", "通过使用SQL模板新增的内容")));
System.out.println("b = " + b);
改
// Db自带的方法Db.update 更新某条数据
Record updatePara = new Record().set("id", 1).set("title", "Db自带的方法Db.update修改后的内容");
boolean update = Db.update("news", "id", updatePara);
System.out.println("update = " + update);
// 通过使用SQL模板 更新某条数据
int a = Db.update(Db.getSqlPara(SQL_KEY + "updateNews", Kv.by("id", 2).set("title", "通过使用SQL模板修改后的内容")));
System.out.println("a = " + a);
查
// 通过使用SQL模板带有参数的查询
Record record = Db.findFirst(Db.getSqlPara(SQL_KEY+"queryNews",Kv.by("id",1)));
System.out.println(record);
// Db自带的方法Db.findById通过主键查询
Record item = Db.findById("news","id",1);
System.out.println("item = " + item);
sql模板:
#namespace("com.active.api.sql")
#sql("queryNews")
SELECT id,title
FROM news
WHERE id = #para(id)
#end
#sql("updateNews")
UPDATE news_item
SET title = #para(title)
WHERE id = #para(id)
#end
#sql("alertNews")
INSERT INTO news_item(
id,title
) values (
#para(id),#para(title)
)
#end
#end
Db提供了很多内置的方法,支持增删改查,也支持批量操作batchUpdate等等,非常好用!
如果内置的方法无法实现你的复杂SQL,可以借助于SQL模板编写自定义SQL
然后具体的SQL模板语法可以参考官方文档:Enjoy SQL 模板
至此,已经整合结束了,你可以使用jfinal独创的Db + Record模式,以及Enjoy SQL模板,写自定义SQL可以写到飞起
4、自动生成映射
好吧还未结束!
走完上面这些步骤有人肯定会发现,怎么没有生成表的映射和实体类呢,万一我需要实体类来获取接收参数,你这不是也不太行?
由于本人喜欢写自定义SQL,用表的实体类比较少,所以这里就简单介绍一下如何整合,具体详细内容可以参考这篇大佬写的文章 链接: springboot 集成 Jfinal activerecord插件
jfinal提供了自动生成映射的方法 生成器的使用,让你无需手动新建映射和实体类,完全通过工具自动创建
① model数据库实体类生成工具 AutoGenerator
public class AutoGenerator {
public static DruidPlugin createDruidPlugin() {
Prop p = PropKit.useFirstFound("wms-generator.txt");
return new DruidPlugin(p.get("jdbcUrl"), p.get("user"), p.get("password").trim(),p.get("driverClassName"));
}
public static DataSource getDataSource() {
DruidPlugin druidPlugin = createDruidPlugin();
druidPlugin.start();
return druidPlugin.getDataSource();
}
public static void main() {
// base model 所使用的包名
String baseModelPackageName = "com.jfinal.model.base";
// base model 文件保存路径
String baseModelOutputDir = PathKit.getWebRootPath() + "/src/main/java/com/jfinal/model/base";
// model 所使用的包名 (MappingKit 默认使用的包名)
String modelPackageName = "com.jfinal.model";
// model 文件保存路径 (MappingKit 与 DataDictionary 文件默认保存路径)
String modelOutputDir = baseModelOutputDir + "/..";
// 创建生成器
Generator generator = new Generator(getDataSource(), baseModelPackageName, baseModelOutputDir, modelPackageName, modelOutputDir);
// 配置是否生成备注
generator.setGenerateRemarks(true);
// 设置数据库方言
generator.setDialect(new MysqlDialect());
// 设置是否生成链式 setter 方法
generator.setGenerateChainSetter(false);
// 添加不需要生成的表名
generator.addExcludedTable("adv");
// 设置是否在 Model 中生成 dao 对象
generator.setGenerateDaoInModel(true);
// 设置是否生成字典文件
generator.setGenerateDataDictionary(false);
// 设置需要被移除的表名前缀用于生成modelName。例如表名 "osc_user",移除前缀 "osc_"后生成的model名为 "User"而非 OscUser
generator.setRemovedTableNamePrefixes("wms");
// 生成
generator.generate();
}
}
以上代码需要改三个地方:baseModelPackageName 、baseModelOutputDir 、modelPackageName
显而易见是你要把model存的目录,找个地方建一个model目录,然后module里建一个base目录
② 生成_MappingKit
可以单独启动这个main方法,也可以讲这个main方法放到启动类中,每次启动时候自动执行
@SpringBootApplication
@ComponentScan({"com.active","com.jfinal"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
System.out.println("启动成功(●'◡'●)");
AutoGenerator.main();
}
}
(注意:我这里是放到了启动类中,如果单独启动这个main方法要把String[] args加上)
public static void main(String[] args) {
// base model 所使用的包名
String baseModelPackageName = "com.jfinal.model.base";
// base model 文件保存路径
String baseModelOutputDir = PathKit.getWebRootPath() + "/src/main/java/com/jfinal/model/base";
...
③ 检查是否自动生成了_MappingKit以及表的映射
当你执行完上一步后,你建的目录中已经自动生成了_MappingKit以及你库里所有表对应的映射
如下图所示
④ 在ActiveRecordPluginConfig中调用_MappingKit.mapping(arp);
@Bean
public ActiveRecordPlugin initActiveRecordPlugin() {
// 配置druid数据库连接池插件
DruidPlugin druidPlugin = this.createDruidPlugin();
// 配置ActiveRecord插件
ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin);
arp.setShowSql(true);
arp.setDevMode(true);
log.info("自动初始化");
// *******************重点在这里
// 所有映射在 MappingKit 中自动化搞定
_MappingKit.mapping(arp);
// *******************
AutoSqlMappingKit.auto(arp,new String[]{"com.active"});
// 开启插件
druidPlugin.start();
arp.start();
log.info("整合ActiveRecord成功");
return arp;
}
⑤ 通过使用表的映射来查询
以下是摘抄自官方文档中的代码示例:
// 创建name属性为James,age属性为25的User对象并添加到数据库
new User().set(“name”, “James”).set(“age”, 25).save();
// 删除id值为25的User
User.dao.deleteById(25);
// 查询id值为25的User将其name属性改为James并更新到数据库
User.dao.findById(25).set(“name”, “James”).update();
// 查询id值为25的user, 且仅仅取name与age两个字段的值
User user = User.dao.findByIdLoadColumns(25, “name, age”);
// 获取user的name属性
String userName = user.getStr(“name”);
// 获取user的age属性
Integer userAge = user.getInt(“age”);
// 查询所有年龄大于18岁的user
List<User> users = User.dao.find(“select * from user where age>18”);
// 分页查询年龄大于18的user,当前页号为1,每页10个user
Page<User> userPage = User.dao.paginate(1, 10, “select *”, “from user where age > ?”, 18);
5、结束
以上便是所以的内容,还有不足的地方就是没用上事务!后续再慢慢研究吧