怀德 發表於 2023-1-29 16:01:00

ORM哪家强?java,c#,php,python,go 逐一对比, 网友直呼:全面客观

<h1 id="前言">前言</h1>
<p>最近一段时间,我使用<code>golang</code>开发了一个新的<code>ORM</code>库。</p>
<p>为了让这个库更好用,我比较研究了各语言的主流<code>ORM</code>库,发现有一些语言的<code>ORM</code>库确实很好用,而有另外一些语言的库那不是一般的难用。</p>
<p>然后我总结了他们呢的一些共性和差异点,于是形成了本文的主要内容。</p>
<p>本文会先说明什么是SQL编写难题,以及探讨一下 <code>code first</code> 和 <code>database first</code> 的优缺点。<br>
然后依据这两个问题的结论去审视目前主流后端语言<code>java</code>, <code>c#</code>, <code>php</code>, <code>python</code>, <code>go</code>各自的orm库,对比研究下他们的优缺点。最后给出总结和参考文档。</p>
<p>如果你需要做技术选型,或者做技术研究,或者类似于我做框架开发,或者单纯地了解各语言的差异,或者就是想吹个牛,建议保存或收藏。如果本文所涉及到的内容有任何不正确,欢迎批评指正。</p>
<p>温馨提示,本文会有一些戏谑或者调侃成分,并非对某些语言或者语言的使用者有任何歧视意见。<br>
如果对你造成了某些伤害,请多包涵。</p>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cc61c2476c594edaa2fc110643042ebd~tplv-k3u1fbpfcp-watermark.image?"></p>
<h1 id="什么是sql编写难题">什么是SQL编写难题</h1>
<p>如果你是做web开发,那么必然需要保存数据到数据库,这个时候你必须熟悉使用sql语句来读写数据库。</p>
<p>sql本身不难,命令也就那几个,关键字也不算多,但是为什么编写sql会成为难题呢?</p>
<p>比如下面的sql</p>
<pre><code class="language-sql">    select * from user
   
    insert user (name,mobile) values ('tang','18600000000')
</code></pre>
<p>它有什么难题? 简单的单表操作嘛,一点难题没有,但凡学过点<code>sql</code>的程序员都能写出来,并且保证正确。我估计比例能超过90%</p>
<p>但是,如果你需要写下面的sql呢?</p>
<pre><code class="language-sql">    SELECT
      article.*,
      person.name as person_name
    FROM article
    LEFT JOIN person ON person.id=article.person_id
    WHERE article.type = 0
    AND article.age IN (18,20)
</code></pre>
<p>这个也不复杂,就是你在做查询列表的时候,会经常用到的联表查询。你是否还有勇气说,写出来的<code>sql</code>绝对正确。我估计比例不超过70%</p>
<p>再稍微复杂点,如果是下面的sql?</p>
<pre><code class="language-sql">    SELECT
      o.*,
      d.department_name,
      (SELECT Sum(so.goods_fee) AS task_detail_target_completed_tem
         FROM sale_order so
         WHERE so.merchant_id = '356469725829664768'
         AND so.create_date BETWEEN (20230127) AND (20230212)
         AND so.delete_state = 2
         AND so.department_id = o.department_id
      ) AS task_detail_target_completed
    FROM task_detail o
    LEFT JOIN department d ON d.department_id=o.department_id
    WHERE o.merchant_id = '356469725829664768'
    AND o.task_id = '356469725972271104768'
</code></pre>
<p>这是我项目里真实的sql语句,目的是统计出所有部门在某时间段内各自的业绩。逻辑上也不太复杂,但你是否还有勇气说,写出来的<code>sql</code>绝对正确。我估计比例不超过40%</p>
<p>如上面的sql所示,SQL编写难题在于以下几方面。</p>
<p><strong>要保证字段正确</strong></p>
<p>应该有的字段不能少,不应该有的字段不能多。</p>
<p>比如你把<code>mobile</code>误打成<code>mobike</code>,这属于拼写错误,但是这个拼写错误只有在实际运行的时候才会告诉你字段名错了。</p>
<p>并且项目越大,表越多,字段越多,这种拼写错误发生的可能性越大。以至于可以肯定的说,100%的可能性会出现。</p>
<p><strong>要特别注意sql语法</strong></p>
<p>例如你在查询的时候必须写<code>from</code>,绝对不能误写成<code>form</code>,但是在实际开发过程中,很容易就打错了。</p>
<p>这种错误,也只有运行的时候才会告诉你语法错了。并且<code>sql</code>越复杂,这种语法错误发生的可能性越大。</p>
<p><strong>编辑器不会有sql的语法提示</strong></p>
<p>常见的编码用的软件,对于sql相关的代码,不会有语法提示,也不会有表名提示,字段名提示。</p>
<p>最终的代码质量如何全凭你的眼力,经验,能力。</p>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c0394e76c7574fd1ace4f73d3513fcd3~tplv-k3u1fbpfcp-watermark.image?"></p>
<p><strong>很显然,既然存在该难题,那么哪个ORM能解决该难题,就应该算得上好,如果不能解决,则不能称之为好。</strong></p>
<h1 id="什么是code-first-和-database-first">什么是code first 和 database first</h1>
<p>这俩概念并不是新概念,但是我估计大多数开发者并不熟悉。</p>
<p>所谓 code first, 相近的词是 model fist, 意思是模型优先,指的是在设计和开发系统时,优先和重点做的工作是设计业务模型,然后根据业务模型去创建数据库。</p>
<p>所谓 database first,意思是数据库优先,指的是在设计和开发系统时,优先和重点做的工作是创建数据库结构,然后去实现业务。</p>
<p>这里我提到了几个词语,可能在不同的语言里叫法不一样,可能不同的人的叫法也不一样,为了下述方便,我们举例子来说。</p>
<h2 id="code-first-例子">code first 例子</h2>
<p>假设我是一个对电商系统完全不懂的小白,手头上也没有如何设计电商系统的资料,我和我的伙伴只是模糊地知道电商系统主要业务就是处理订单。</p>
<p>然后我大概会知道这个订单,主要的信息包括哪个用户下单,什么时间下单,有哪几种商品,数量分别是多少,根据这些已有的信息,我可以设计出来业务模型如下</p>
<pre><code class="language-java">public class OrderModel {
    //订单编号
    Integer orderId;
    //用户编号
    Integer userId;
    //订单时间
    Integer createTime;
    //订单详情(包含商品编号,商品数量)
    StringorderDetail;
}
</code></pre>
<p>很简单,对吧,这个模型很匹配我目前对系统的认知。接下来会做各种业务逻辑,最后要做的是将订单模型的数据保存到数据库。但是在保存数据到数据库的时候,就有一些考虑了。</p>
<p>我可以将上面<code>OrderModel</code>业务模型建立一张对应表,里面的4个属性,对应数据表里的4个字段,这完全可以。<br>
但是我是电商小白,不是数据库小白啊,这样存储的话,肯定不利于统计订单商品的。</p>
<p>所以我换一种策略,将<code>OrderModel</code>的信息进行拆分,将前三个属性 orderId, userId, createTime 放到一个新的类里。<br>
然后将 orderDetail 的信息进行再次分解,放到另一个类里</p>
<pre><code class="language-java">public class OrderEntity {
    Integer orderId;
    Integer userId;
    Integer createTime;
}

public class OrderDetailEntity {
    Integer orderDetailId;
    Integer orderId;
    Integer goodsId;
    Integer goodsCount;
}
</code></pre>
<p>最后,在数据库建立两张表<code>order</code>,<code>order_detail</code>,表结构分别对应类<code>OrderEntity</code>,<code>OrderDetailEntity</code>的结构。</p>
<p>至此,我们完成了从业务模型<code>OrderModel</code>到数据表<code>order</code>,<code>order_detail</code>的过程。</p>
<p>这就是 code first ,注意这个过程的关键点,我优先考虑的是模型和业务实现,后面将业务模型数据进行分解和保存是次要的,非优先的。</p>
<h2 id="database-first-例子">database first 例子</h2>
<p>假设我是一个对电商系统非常熟悉的老鸟,之前做过很多电商系统,那么我在做新的电商系统的时候,就完全可以先设计数据库。</p>
<p><code>order</code>表放订单主要数据,里面有xxx几个字段,分别有什么作用,有哪些状态值</p>
<p><code>order_detail</code>表放订单详情数据,,里面有xxx几个字段,分别有什么作用</p>
<p>这些都可以很清楚和明确。然后根据表信息,生成<code>OrderEntity</code>,以及<code>OrderDetailEntity</code>即可开始接下来的编码工作。这种情况下<code>OrderModel</code>可能有,也可能没有。</p>
<p>这就是 database first ,注意这个过程的关键点,我优先考虑的是数据库结构和数据表结构。</p>
<h2 id="两种方式对比">两种方式对比</h2>
<p>code first 模式下, 系统设计者优先考虑的是业务模型<code>OrderModel</code>, 它可以描述清楚一个完整业务,包括它的所有业务细节(什么人的订单,什么时候的订单,订单包含哪些商品,数量多少),有利于设计者对于系统的整体把控。</p>
<p>database first 模式下, 系统设计者优先考虑的是数据表<code>order</code>,<code>order_detail</code>,他们中任何一张表都不能完整的描述清楚一个完整业务,只能够描述局部细节,不利于设计者对于系统的整体把控。</p>
<p>在这里,调皮的同学会问,在 database first 模式下, 我把<code>order</code>,<code>order_detail</code>的信息一起看,不就知道完整的业务细节了吗?</p>
<p>确实是这样,但这里有一个前提,前提是你必须明确的知道<code>order</code>,<code>order_detail</code>是需要一起看的,而你知道他们需要一起看的前提是你了解电商系统。 如果你设计的不是电商系统,而是电路系统,你还了解吗?还知道哪些表需要一起看吗?</p>
<p>至此,我们可以有以下粗浅的判断:</p>
<p><strong>对于新项目,不熟悉的业务,code first 模式更适合一些</strong></p>
<p><strong>对于老项目,熟悉的业务,database first 模式更合适一些</strong></p>
<p><strong>如果两种模式都可以的话,优先使用 code first 模式,便于理解业务,把控项目</strong></p>
<p><strong>如果哪个ORM支持 code first , 我们可以稍稍认为它更好一些</strong></p>
<h1 id="java体系的orm">Java体系的orm</h1>
<p>Java语言是web开发领域处于领先地位,这一点无可置疑。它的优点很明显,但是缺点也不是没有。</p>
<p>国内应用比较广泛的orm是Mybatis,以及衍生品Mybatis-plus等</p>
<p>实际上Mybatis团队还出了另外一款产品,MyBatis Dynamic SQL,国内我见用的不多,讨论都较少。英文还可以的同学,可以看下面的文档。</p>
<p>另外还有 jOOQ, 实际上跟 MyBatis Dynamic SQL 非常类似,有兴趣的可以去翻翻</p>
<p>下面,我们举一些例子,来对比一下他们的基本操作</p>
<h2 id="java体系的mybatis">Java体系的Mybatis</h2>
<p>单就orm这一块,国内用的最多的应该是Mybatis,说到它的使用体验吧,那简直是一言难尽。</p>
<p>你需要先定义模型,然后编写<code>xml</code>文件用来映射数据,然后创建mapper文件,用来执行<code>xml</code>里定于的sql。<br>
从这个流程可以看出,中间的<code>xml</code>文件起到核心作用,里面不光有数据类型转换,还有最核心的<code>sql</code>语句。</p>
<p>典型的<code>xml</code>文件内容如下</p>
<pre><code class="language-xml">&lt;mapper namespace="xxx.mapper.UserMapper"&gt;
    &lt;insert id="insertUser" parameterType="UserEntity"&gt;
      insert into user (id,name,mobile)
      values (#{id},#{name},#{mobile})
    &lt;/insert&gt;

    &lt;update id="updateUser" parameterType="UserEntity"&gt;
      update user set
      name = #{name},
      mobile = #{mobile}
      where id = #{id}
    &lt;/update&gt;

    &lt;delete id="deleteUser"&gt;
      delete from user where id = #{id}
    &lt;/delete&gt;

    &lt;select id="selectUsers" resultType="UserVO"&gt;
      select u.*, (select count(*) from article a where a.uid=u.id) as article_count
      from user u
      where u.id = #{id}
    &lt;/select&gt;
&lt;/mapper&gt;
</code></pre>
<p>你在编写这个<code>xml</code>文件的时候,这个手写sql没有本质区别,一定会遇到刚才说到的<code>SQL编写难题</code>。</p>
<h2 id="java体系的mybatis-plus">Java体系的Mybatis-plus</h2>
<p>这里有必要提一下 Mybatis-plus,它是国内的团队开发出来的工具,算是对Mybatis的扩展吧,它减少了<code>xml</code>文件内容的编写,减少了一些开发的痛苦。比如,你可以使用如下的代码来完成以上相同的工作</p>
<pre><code class="language-java">    userService.insert(user);

    userService.update(user);

    userService.deleteById(user);

    List&lt;UserEntity&gt; userList = userService.selectList(queryWrapper);
</code></pre>
<p>完成这些工作,你不需要编写任何<code>xml</code>文件,也不需要编写<code>sql</code>语句,如之前所述,减少了一些开发的痛苦。</p>
<p>但是,请你注意我的用词,是减少了一些。</p>
<p>对于连表操作,嵌套查询等涉及到多表操作的事情,它就不行了,为啥不行,因为根本就不支持啊。<br>
遇到这种情况,你就老老实实的去写<code>xml</code>吧,然后你还会遇到刚才说到的<code>SQL编写难题</code>。</p>
<h2 id="java体系的mybatis3-dynamic-sql">Java体系的Mybatis3 Dynamic Sql</h2>
<p>值得一提的是Mybatis3 Dynamic Sql,翻译一下就是动态sql。还是刚才说的国内我见用的不多,讨论都较少,但是评价看上去挺好。</p>
<p>简单来说,可以根据不同条件拼接出sql语句。不同于上面的Mybatis,这些sql语句是程序运行时生成的,而不是提前写好的,或者定义好的。</p>
<p>它的使用流程是,先在数据库里定义好数据表,然后创建模型文件,让然后通过命令行工具,将每一个表生成如下的支持文件</p>
<pre><code class="language-java">public final class PersonDynamicSqlSupport {
    public static final Person person = new Person();
    public static final SqlColumn&lt;Integer&gt; id = person.id;
    public static final SqlColumn&lt;String&gt; firstName = person.firstName;
    public static final SqlColumn&lt;LastName&gt; lastName = person.lastName;
    public static final SqlColumn&lt;Date&gt; birthDate = person.birthDate;
    public static final SqlColumn&lt;Boolean&gt; employed = person.employed;
    public static final SqlColumn&lt;String&gt; occupation = person.occupation;
    public static final SqlColumn&lt;Integer&gt; addressId = person.addressId;

    public static final class Person extends SqlTable {
      public final SqlColumn&lt;Integer&gt; id = column("id", JDBCType.INTEGER);
      public final SqlColumn&lt;String&gt; firstName = column("first_name", JDBCType.VARCHAR);
      public final SqlColumn&lt;LastName&gt; lastName = column("last_name", JDBCType.VARCHAR, "examples.simple.LastNameTypeHandler");
      public final SqlColumn&lt;Date&gt; birthDate = column("birth_date", JDBCType.DATE);
      public final SqlColumn&lt;Boolean&gt; employed = column("employed", JDBCType.VARCHAR, "examples.simple.YesNoTypeHandler");
      public final SqlColumn&lt;String&gt; occupation = column("occupation", JDBCType.VARCHAR);
      public final SqlColumn&lt;Integer&gt; addressId = column("address_id", JDBCType.INTEGER);

      public Person() {
            super("Person");
      }
    }
}
</code></pre>
<p>可以看出,这里的主要功能能是将表内的字段,与java项目里的类里面的属性,做了一一映射。</p>
<p>接下来你在开发的时候,就不用关心表名,以及字段名了,直接使用刚才生成的类,以及类下面的那些属性。具体如下</p>
<pre><code class="language-java">      SelectStatementProvider selectStatement = select(id.as("A_ID"), firstName, lastName, birthDate, employed,occupation, addressId)
      .from(person)
      .where(id, isEqualTo(1))
      .or(occupation, isNull())
      .build()
      .render(RenderingStrategies.MYBATIS3);

      List&lt;PersonRecord&gt; rows = mapper.selectMany(selectStatement);
</code></pre>
<p>如上面的代码,好处有以下四点</p>
<ol>
<li>你不再需要手写sql</li>
<li>也不用在意字段名了,因为使用的都是类,或者属性,编写代码的时候编辑器会有提示,编译的时候如果有错误也会提示,实际运行的时候就不会有问题了。</li>
<li>联表查询,嵌套查询啥的,也都支持</li>
<li>完美避开了<code>SQL编写难题</code></li>
</ol>
<p>当然带来了额外的事情,比如你要使用工具来生成<code>PersonDynamicSqlSupport</code>类,比如你要先建表。</p>
<p>先建表这事儿,很明显就属于 <code>database first</code> 模式。</p>
<p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/06cbc082c3c24647a478198ac3fd9dac~tplv-k3u1fbpfcp-watermark.image?"></p>
<h1 id="c体系的orm">C#体系的orm</h1>
<p>C# 在工业领域,游戏领域用的多一些,在web领域少一些。</p>
<p>它也有自己的orm,名字叫 Entity Framework Core, 一直都是微软公司在维护。</p>
<p>下面是一个典型的联表查询</p>
<pre><code class="language-c#">    var id = 1;
    var query = database.Posts
                .Join(database.Post_Metas,
                  post =&gt; post.ID,
                  meta =&gt; meta.Post_ID,
                  (post, meta) =&gt; new { Post = post, Meta = meta }
                )
                .Where(postAndMeta =&gt; postAndMeta.Post.ID == id);
</code></pre>
<p>这句代码的主要作用是,将数据库里的Posts表,与Post_Metas表做内联操作,然后取出Post.ID等于1的数据</p>
<p>这里出现的Post,以及Meta都是提前定义好的模型,也就是类。 Post.ID 是 Post 的一个属性,也是提前定义好的。</p>
<p>整个功能的优点很多,你不再需要手写sql,不需要关心字段名,不需要生成额外类,也不会有语法错误,你只需要提前定义好模型,完全没有<code>SQL编写难题</code>,很明显就属于 <code>code first</code> 模式。</p>
<p>对比java的Mybatis以及Mybatis3 Dynamic Sql来说,你可以脑补一下下面的场景</p>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c0475f2473534503b9555c7f56012158~tplv-k3u1fbpfcp-watermark.image?"></p>
<h1 id="php体系的orm">PHP体系的orm</h1>
<p>php体系内,框架也非常多,比如常见的<code>laravel</code>,<code>symfony</code>,这里我们就看这两个,比较有代表性</p>
<h2 id="php体系的laravel">PHP体系的laravel</h2>
<p>使用php语言开发web应用的也很多,其中比较出名的是<code>laravel</code>框架,比较典型的操作数据库的代码如下</p>
<pre><code class="language-php">$user = DB::table('users')-&gt;where('name', 'John')-&gt;first();
</code></pre>
<p>这里没有使用模型(就算使用了也差不多),代码里出现的 users 就是数据库表的名字, name 是 users 表里的字段名,他们是被直接写入代码的</p>
<p>很明显它会产生<code>SQL编写难题</code></p>
<p>并且,因为是先设计数据库,肯定也属于 <code>database first</code> 模式</p>
<h2 id="php体系的symfony">PHP体系的symfony</h2>
<p>这个框架历史也比较悠久了,它使用了 Doctrine 找个类库作为orm</p>
<p>使用它之前,也需要先定义模型,然后生成支持文件,然后建表,但是在实际使用的时候,还是和laravel一样,表名,字段名都需要硬编码</p>
<pre><code class="language-php">$repository = $this-&gt;getDoctrine()-&gt;getRepository('AppBundle:Product');
&nbsp;
// query for a single product by its primary key (usually "id")
// 通过主键(通常是id)查询一件产品
$product = $repository-&gt;find($productId);
&nbsp;
// dynamic method names to find a single product based on a column value
// 动态方法名称,基于字段的值来找到一件产品
$product = $repository-&gt;findOneById($productId);
$product = $repository-&gt;findOneByName('Keyboard');

// query for multiple products matching the given name, ordered by price
// 查询多件产品,要匹配给定的名称和价格
$products = $repository-&gt;findBy(
    array('name' =&gt; 'Keyboard'),
    array('price' =&gt; 'ASC')
);
</code></pre>
<p>很明显它也会产生<code>SQL编写难题</code></p>
<p>另外,并不是先设计表,属于 <code>code first</code> 模式</p>
<p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/764abce80d6d4557825fab1b325c44c2~tplv-k3u1fbpfcp-watermark.image?"></p>
<h1 id="python体系的orm">python体系的orm</h1>
<p>在python领域,有一个非常著名的框架,叫django, 另外一个比较出名的叫flask, 前者追求大而全,后者追求小而精</p>
<h2 id="python体系的django">python体系的django</h2>
<p>django推荐的开发方法,也是先建模型,但是在查询的时候,这建立的模型,基本上毫无用处</p>
<pre><code class="language-python">    res=models.Author.objects.filter(name='jason').values('author_detail__phone','name')
    print(res)
    # 反向
    res = models.AuthorDetail.objects.filter(author__name='jason')# 拿作者姓名是jason的作者详情
    res = models.AuthorDetail.objects.filter(author__name='jason').values('phone','author__name')
    print(res)


    # 2.查询书籍主键为1的出版社名称和书的名称
    res = models.Book.objects.filter(pk=1).values('title','publish__name')
    print(res)
    # 反向
    res = models.Publish.objects.filter(book__id=1).values('name','book__title')
    print(res)
</code></pre>
<p>如上连表查询的代码,values('title','publish__name') 这里面写的全都是字段名,硬编码进去,进而产生sql语句,查询出结果</p>
<p>很显然,它也会产生<code>SQL编写难题</code></p>
<p>另外,并不是先设计表,属于 <code>code first</code> 模式</p>
<h2 id="python体系的flask">python体系的flask</h2>
<p>flask本身没有orm,一般搭配 sqlalchemy 使用</p>
<p>使用 sqlalchemy 的时候,一般也是先建模型,然后查询的时候,可以直接使用模型的属性,而无须硬编码</p>
<pre><code class="language-python">result = session.               
query(User.username,func.count(Article.id)).
join(Article,User.id==Article.uid).
group_by(User.id).
order_by(func.count(Article.id).desc()).
all()
</code></pre>
<p>如上 Article.id 即是 Article 模型下的 id 属性</p>
<p>很显然,它不会产生<code>SQL编写难题</code></p>
<p>另外,并不是先设计表,属于 <code>code first</code> 模式</p>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b68e0f7e1e7e45519fa4334b10148f00~tplv-k3u1fbpfcp-watermark.image?"></p>
<h1 id="go体系的orm">go体系的orm</h1>
<p>在go体系,orm比较多,属于百花齐放的形态,比如国内用的多得gorm以及gorm gen,国外比较多的ent, 当然还有我自己写的 arom</p>
<h2 id="go体系下的gorm">go体系下的gorm</h2>
<p>使用gorm,一般的流程是你先建立模型,然后使用类似如下的代码进行操作</p>
<pre><code class="language-go">type User struct {
Idint
Age int
}

type Order struct {
UserId   int
FinishedAt *time.Time
}

query := db.Table("order").
Select("MAX(order.finished_at) as latest").
Joins("left join user user on order.user_id = user.id").
Where("user.age &gt; ?", 18).
Group("order.user_id")

db.Model(&amp;Order{}).
Joins("join (?) q on order.finished_at = q.latest", query).
Scan(&amp;results)
</code></pre>
<p>这是一个嵌套查询,虽然定义了模型,但是查询的时候并没有使用模型的属性,而是输入硬编码</p>
<p>很显然,它会产生<code>SQL编写难题</code></p>
<p>另外,是先设计模型,属于 <code>code first</code> 模式</p>
<h2 id="go体系下的gorm-gen">go体系下的gorm gen</h2>
<p>gorm gen 是 gorm 团队开发的另一款产品,和mybaits下的Mybatis3 Dynamic Sql比较像</p>
<p>它的流程是 先创建数据表,然后使用工具生成结构体(类)和支持代码, 然后再使用生成的结构体</p>
<p>它生成的比较关键的代码如下</p>
<pre><code class="language-go">func newUser(db *gorm.DB) user {
        _user := user{}

        _user.userDo.UseDB(db)
        _user.userDo.UseModel(&amp;model.User{})

        tableName := _user.userDo.TableName()
        _user.ALL = field.NewAsterisk(tableName)
        _user.ID = field.NewInt64(tableName, "id")
        _user.Name = field.NewString(tableName, "name")
        _user.Age = field.NewInt64(tableName, "age")
        _user.Balance = field.NewFloat64(tableName, "balance")
        _user.UpdatedAt = field.NewTime(tableName, "updated_at")
        _user.CreatedAt = field.NewTime(tableName, "created_at")
        _user.DeletedAt = field.NewField(tableName, "deleted_at")
        _user.Address = userHasManyAddress{
                db: db.Session(&amp;gorm.Session{}),

                RelationField: field.NewRelation("Address", "model.Address"),
        }

        _user.fillFieldMap()

        return _user
}
</code></pre>
<p>注意看,其中大多数代码的作用是啥?不意外,就是将结构体的属性与表字段做映射关系</p>
<p>_user.Name 对应 name<br>
_user.Age 对应 age</p>
<p>如此,跟mybaits下的Mybatis3 Dynamic Sql的思路非常一致</p>
<p>典型查询代码如下</p>
<pre><code class="language-go">u := query.User
err := u.WithContext(ctx).
    Select(u.Name, u.Age.Sum().As("total")).
    Group(u.Name).
    Having(u.Name.Eq("group")).
    Scan(&amp;users)

// SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"
</code></pre>
<p>这是一个分组查询,定义了模型,也使用了模型的属性。</p>
<p>但是呢,它需要使用工具生成额外的支持代码,并且需要先定义数据表</p>
<p>很显然,它不会产生<code>SQL编写难题</code></p>
<p>另外,它是先设计表,属于 <code>database first</code> 模式</p>
<h2 id="go体系下的ent">go体系下的ent</h2>
<p>ent 是 facebook公司开发的Orm产品,与 gorm gen 有相通,也有不同</p>
<p>相同点在于,都是利用工具生成实体与数据表字段的映射关系</p>
<p>不同点在于gorm gen先有表和字段,然后生成实体</p>
<p>ent是没有表和字段,你自己手动配置,配置完了一起生成实体和建表</p>
<p>接下来,看一眼ent生成的映射关系</p>
<pre><code class="language-go">const (
        // Label holds the string label denoting the user type in the database.
        Label = "user"
        // FieldID holds the string denoting the id field in the database.
        FieldID = "id"
        // FieldName holds the string denoting the name field in the database.
        FieldName = "name"
        // FieldAge holds the string denoting the age field in the database.
        FieldAge = "age"
        // FieldAddress holds the string denoting the address field in the database.
        FieldAddress = "address"
        // Table holds the table name of the user in the database.
        Table = "users"
)
</code></pre>
<p>有了映射关系,使用起来就比较简单了</p>
<pre><code class="language-go">u, err := client.User.
      Query().
      Where(user.Name("realcp")).
      Only(ctx)
</code></pre>
<p>注意,这里没有硬编码</p>
<p>它需要使用工具生成额外的支持代码,并且需要先配置表结构</p>
<p>很显然,它不会产生<code>SQL编写难题</code></p>
<p>另外,它属于先设计表,属于 <code>database first</code> 模式</p>
<h2 id="go体系下的aorm">go体系下的aorm</h2>
<p>aorm 是我自己开发的orm库,吸取了ef core 的一些优点,比较核心的步骤如下</p>
<p>和大多数orm一样,需要先建立模型,比如</p>
<pre><code class="language-go">    type Person struct {
      Id         null.Int    `aorm:"primary;auto_increment" json:"id"`
      Name       null.String `aorm:"size:100;not null;comment:名字" json:"name"`
      Sex      null.Bool   `aorm:"index;comment:性别" json:"sex"`
      Age      null.Int    `aorm:"index;comment:年龄" json:"age"`
      Type       null.Int    `aorm:"index;comment:类型" json:"type"`
      CreateTime null.Time   `aorm:"comment:创建时间" json:"createTime"`
      Money      null.Float`aorm:"comment:金额" json:"money"`
      Test       null.Float`aorm:"type:double;comment:测试" json:"test"`
    }
       
</code></pre>
<p>然后实例化它,并且保存起来</p>
<pre><code class="language-go">    //Instantiation the struct
    var person = Person{}
       
    //Store the struct object
    aorm.Store(&amp;person)
</code></pre>
<p>然后即可使用</p>
<pre><code class="language-go">    var personItem Person
    err := aorm.Db(db).Table(&amp;person).WhereEq(&amp;person.Id, 1).OrderBy(&amp;person.Id, builder.Desc).GetOne(&amp;personItem)
    if err != nil {
      fmt.Println(err.Error())
    }
</code></pre>
<p>很显然,它不会产生<code>SQL编写难题</code></p>
<p>另外,它属于先设计模型,属于 <code>code first</code> 模式</p>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a15f6f776823411ebfa599111b796615~tplv-k3u1fbpfcp-watermark.image?"></p>
<h1 id="总结">总结</h1>
<p>本文,我们提出了两个衡量orm功能的原则,并且对比了几大主流后端语言的orm,汇总列表如下</p>
<table>
<thead>
<tr>
<th>框架</th>
<th>语言</th>
<th>SQL编写难题</th>
<th>code first</th>
<th>额外创建文件</th>
</tr>
</thead>
<tbody>
<tr>
<td>MyBatis 3</td>
<td>java</td>
<td>有难度</td>
<td>不是</td>
<td>需要</td>
</tr>
<tr>
<td>MyBatis-Plus</td>
<td>java</td>
<td>有难度</td>
<td>不是</td>
<td>不需要</td>
</tr>
<tr>
<td>MyBatis Dynamic SQL</td>
<td>java</td>
<td>没有</td>
<td>不是</td>
<td>需要</td>
</tr>
<tr>
<td>jOOQ</td>
<td>java</td>
<td>没有</td>
<td>不是</td>
<td>需要</td>
</tr>
<tr>
<td>ef core</td>
<td>c#</td>
<td>没有</td>
<td>是</td>
<td>不需要</td>
</tr>
<tr>
<td>laravel</td>
<td>php</td>
<td>有难度</td>
<td>不是</td>
<td>不需要</td>
</tr>
<tr>
<td>symfony</td>
<td>php</td>
<td>有难度</td>
<td>不是</td>
<td>需要</td>
</tr>
<tr>
<td>django</td>
<td>python</td>
<td>有难度</td>
<td>是</td>
<td>不需要</td>
</tr>
<tr>
<td>sqlalchemy</td>
<td>python</td>
<td>没有</td>
<td>是</td>
<td>不需要</td>
</tr>
<tr>
<td>grom</td>
<td>go</td>
<td>有难度</td>
<td>是</td>
<td>不需要</td>
</tr>
<tr>
<td>gromgen</td>
<td>go</td>
<td>没有</td>
<td>不是</td>
<td>需要</td>
</tr>
<tr>
<td>ent</td>
<td>go</td>
<td>没有</td>
<td>不是</td>
<td>需要</td>
</tr>
<tr>
<td>aorm</td>
<td>go</td>
<td>没有</td>
<td>是</td>
<td>不需要</td>
</tr>
</tbody>
</table>
<p>单就从这张表来说,不考虑其他条件,在做orm技术选型时,</p>
<p>如果你使用java语言,请选择 MyBatis Dynamic SQL 或者 jOOQ,因为选择他们不会有<code>SQL编写难题</code></p>
<p>如果你使用c#语言,请选择 ef core, 这已经是最棒的orm了,不会有<code>SQL编写难题</code>,支持<code>code first</code>,并且不需要额外的工作</p>
<p>如果你使用php语言,请选择 laravel 而不是 symfony, 反正都有<code>SQL编写难题</code>,那就挑个容易使用的</p>
<p>如果你使用python语言,请选择 sqlalchemy 库, 不会有<code>SQL编写难题</code>,支持<code>code first</code>,并且不需要额外的工作</p>
<p>如果你使用go语言,请选择 aorm 库, 不会有<code>SQL编写难题</code>,支持<code>code first</code>,并且不需要额外的工作</p>
<p>好了,文章写两天了,终于写完了。如果对你有帮助,记得点赞,收藏,转发。</p>
<p>如果我有说的不合适,或者不对的地方,请在下面狠狠的批评我。</p>
<p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/892aab3b18554760953693f39e1b4fd0~tplv-k3u1fbpfcp-watermark.image?"></p>
<h1 id="参考文档">参考文档</h1>
<p>MyBatis 3<br>
MyBatis-Plus<br>
MyBatis Dynamic SQL<br>
jOOQ: The easiest way to write SQL in Java<br>
Entity Framework Core 概述 - EF Core | Microsoft Learn<br>
数据库和Doctrine ORM - Symfony开源 - Symfony中国 (symfonychina.com)<br>
Django(ORM查询、多表、跨表、子查询、联表查询) - 知乎 (zhihu.com)<br>
Sqlalchemy join连表查询_FightAlita的博客-CSDN博客_sqlalchemy 连表查询<br>
Gorm + Gen自动生成数据库结构体_Onemorelight95的博客-CSDN博客_gorm 自动生成<br>
tangpanqing/aorm: Operate Database So Easy For GoLang Developer (github.com)</p><br><br>
来源:https://www.cnblogs.com/tangpanqing/p/17072958.html
頁: [1]
查看完整版本: ORM哪家强?java,c#,php,python,go 逐一对比, 网友直呼:全面客观