MyBatis还是JPA?终于有答案了!

    科技2025-02-07  13

    原创:猿逻辑,欢迎分享,转载请保留出处。

    对于一个和数据库打交道的程序员来说,很快会面临着一个艰难的选择。到底是选择MyBatis还是JPA呢?

    很多人说,技术选择,都要根据需求来,这个没错。但是,除了需求,还有很重要的一个环节,那就是队友的水平。如果你选择了一些比较高级的技术,那么就是在给整个团队埋坑。

    JPA的抽象层次更高,代码写起来也更简洁,但是它一点都不简单。虽然经过了多次的培训,我呆过的几个团队,还是把它用的和屎一样。

    我扔掉了JPA

    我仔细想了一下,有下面几点原因,造成了JPA在很多团队根本就玩不下去。

    JPA适合业务模型固定的场景,适合比较稳定的需求。但是国内这种朝三暮四的需求风格,产品经理这种传话筒式的设计模式,造成了需求的泛滥和不确定。JPA在这种模式下就是渣。

    JPA的技术要求比较高。不要怀疑,你刚开始用起里可能觉得非常简单。但随着你的深入使用,你会发现这是一个灾难。里面的各种转换和缓存,会把人绕晕。而大多数的快餐程序员是不想要了解这些的。

    很多程序员很会写SQL,所以很多SQL语句长的很胖,长的要命。业务混乱,多张表关联,我甚至见过上百张业务表关联的复杂业务。DBA无奈之下,通常都会有sql审核。JPA搞sql审核?还是弱了一点。

    所以,不是JPA不好,而是它不符合国情而已。想要在公司内推行JPA,你需要给我一个稳定的产品团队、一个牛X的技术团队才行。

    所以,大多数公司宁可写一堆重复的、乱七八糟的Mybaits代码,也不会轻易尝试JPA,这是符合逻辑的,符合事物发展规律的。

    所以,我们下面的文章就是来讨论MyBatis的,来看一下Mybaits到底要怎么写才算优雅。

    MyBatis为什么不好用

    优秀的程序员都是很懒的。所以很多人不想设计实体的sql。JPA可以直接根据Java的实体代码,生成sql的库表,这在使用Mybatis的人来看,是非常羡慕的。

    使用MyBatis,要倒着来。需要先设计库表,然后根据库表反向生成一堆Java代码和配置文件。

    这个代码生成器,就是mybatis-generator。

    但是,请注意。这个生成器生成的代码,有四种模式!!!这就是最让初学者难受的地方。如果你也是刚接触MyBatis,强烈推荐只关注下面第一种模式。

    MyBatis3 这种模式就是我们常用的方式,会生成domain类、Example类、mapper映射文件等。它生成的信息比较啰嗦,内容几乎无法改动。对于项目中自己写的sql,一般都采用手写的方式再写一份,而不是改动原来的文件。

    MyBatis3Simple 上面这种模式的简易代码生成模式,缺少一些东西,但很简洁。对MyBatis没有经验,不推荐使用它。

    MyBatis3DynamicSql 这是通过Builder模式实现的动态SQL特性,你还需要加入额外的jar包。加上它之后,其实和JPA是有点相似的。既然如此,那为何不直接使用JPA呢?所以这个DSQL虽然是默认的生成行为,但是非常不推荐。

    MyBatis3Kotlin 这个不废话。就是生成Kotlin版的一些配置和代码信息。

    所以,下面仅仅介绍MyBatis3模式的代码生成。

    要使用它,需要在pom.xml里加入它的依赖。

    <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <optional>true</optional> <scope>test</scope> <version>1.4.0</version> </dependency>

    我个人喜欢使用Java代码来操作代码生成这个过程,所以下面就是生成代码的代码。

    public class MBGTool { public static void main(String[] args) throws Exception { List<String> warnings = new ArrayList<>(); boolean overwrite = true; InputStream configFile = MBGTool.class.getResourceAsStream("/generator/generatorConfig.xml"); 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); } }

    从代码中,我们可以看到需要配置一个generatorConfig.xml文件,用来规定怎么生成代码文件。

    <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <context id="simple" targetRuntime="MyBatis3"> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mbye" userId="root" password="root" /> <javaModelGenerator targetPackage="com.github.javarunfast.mbye.domain" targetProject="src/main/java"> <property name="enableSubPackages" value="true"/> <property name="trimStrings" value="true"/> </javaModelGenerator> <sqlMapGenerator targetPackage="com.github.javarunfast.mbye.mapper" targetProject="src/main/resources"/> <javaClientGenerator type="XMLMAPPER" targetPackage="com.github.javarunfast.mbye.mapper" targetProject="src/main/java"> <property name="enableSubPackages" value="true"/> </javaClientGenerator> <table tableName="test"/> </context> </generatorConfiguration>

    运行我们的MBGTool文件之后,就可以生成MyBatis的代码了。

    怎么写代码最优雅

    但是,我这里并不是要推荐你使用这种模式。因为,它生成了一大堆无用的文件。假如你的项目使用了sonar这样的代码质量审查工具,你会发现很多飘红的地方,还有那要命的覆盖率问题。

    怎么办?

    经过我多年的摸索,我现在推荐一种非常好用的写法。自从我采用了这种方式之后,就再也没有换过。

    第一、不需要代码生成器了

    数据表的设计,还有domain的书写,全部靠手工。这样我们的代码,如果有必要,还可以迁移到JPA上去。这种模式还能顺便学习一下Java里面的数据类型,是如何和SQL里的数据类型一一对应的。在做表设计的时候,顺便能够了解一些背后的原理。

    第二、不需要写映射文件了

    生成器生成的东西,确实是有一堆无用的逻辑。比如我的某个数据表,根本不需要提供查询所有和删除这种动作,它还是默认提供了。

    在这种简约模式下,我们直接手写Mapper文件,然后只声明所需要的接口方法就可以了。

    @Mapper public interface AccountBasicMapper { @Insert("AccountBasicMapper/insert.sql") void insert(@Param("r") AccountBasic record); }

    可以看到,里面有一个Insert注解,我们传入了一个具体的domain,然后,就可以在AccountBasicMapper目录下的insert.sql文件里,书写具体的sql语句了。

    sql语句样例如下:

    INSERT INTO account_basic( account_id, nick_name, password, sex, state, photo_url, created, modified, version )VALUES ( <@p name="r.accountId"/>, <@p name="r.nickName"/>, <@p name="r.password"/>, <@p name="r.sex"/>, <@p name="r.state"/>, <@p name="r.photoUrl"/>, <@p name="r.created"/>, <@p name="r.modified"/>, <@p name="r.version"/> )

    那么这是什么语法呢?它又是如何知道是这样配置的呢?这就需要引入MyBatis的脚本语言配置功能。在这里,我们使用的freemark的模版。

    不要忘了加入它的依赖。

    <dependency> <groupId>org.mybatis.scripting</groupId> <artifactId>mybatis-freemarker</artifactId> <version>1.2.2</version> </dependency>

    然后,在yaml文件里做上相应的配置就ok了。

    mybatis: check_config_location: false scripting-language-driver: freemarker: template-file: base-dir: mappers/ path-provider: includes-package-path: false separate-directory-per-mapper: false

    这种方式的好处和坏处

    我个人是非常喜欢这种模式的。因为它有下面几个好处:

    用什么写什么,代码量少,简洁优雅。

    SQL集中,不用分散在代码里,xml里,或者注解里。方便DBA进行SQL审核。由于没了xml的干扰,SQL反而更加简洁了。

    一个DAO方法一个sql文件,模式单一可控。

    MyBatis的功能优势可以全部发挥,无缝集成。

    当然,缺点也是显而易见的。

    即使变了个参数,也要修改很多sql文件。

    需要为每一个方法配一个sql文件,即使这是个很弱智的插入查询方法。

    不过,我并不认为这是个问题。每一个方法配备一个sql文件,代码写起来反而更加简单了。当出现问题的时候,也不用根据逻辑进行跟踪定位到拼接后的SQL语句。我现在,只需要拿到对应方法的SQL文件,就可以改吧改吧,直接在sql终端里执行调试。这样,sql优化也变的简单了。

    当然,一个人一个习惯。我个人喜欢这种模式,而且在我的团队里推行这种模式,发现运行的也很好。另外,程序员为了少写重复的sql代码,在设计Dao接口的时候,反而更加认真了。

    这可能是一个额外的收获吧。

    本文转载至公众号《猿逻辑》,敬请各位小伙伴关注。猿逻辑是北京一线大厂小哥小Q创建,旨在为广大程序员提供免费实战课程。关注他,it路上不迷路。


    推荐阅读:

    一图解千愁,jvm内存从来没有这么简单过!失联的架构师,只留下一段脚本架构师写的BUG,非比寻常nginx工程师,需要上承天命,下召九幽实力解剖一枚挖矿脚本,风骚操作亮瞎双眼又一P1故障,锅比脸圆传统企业的人才们,先别忙着跳“互联网”!面试官很牛,逼我尿遁又一批长事务,P0故障谁来背锅?一天有24个小时?别开玩笑了!《程序人生》杀机!可怕的“浏览器指纹”,让你在互联网上,无处可藏2w字长文,让你瞬间拥有「调用链」开发经验996的乐趣,你是无法想象的作为高级Java,你应该了解的Linux知识(非广告)必看!java后端,亮剑诛仙(最全知识点)学完这100多技术,能当架构师么?(非广告)Linux上,最常用的一批命令解析(10年精选)

    Processed: 0.011, SQL: 8