视觉角度: jfinal的Model与Beetlsql比较
视觉角度: jfinal的Model与Beetlsql比较
渔泯小镇 发表于7个月前
视觉角度: jfinal的Model与Beetlsql比较
  • 发表于 7个月前
  • 阅读 3542
  • 收藏 123
  • 点赞 18
  • 评论 97

华为云·免费上云实践>>>   

JFinal 是目前在 git.oschina.net java中关注最多的项目.

亲自用JFinal开发过有上百张表的项目.项目完结后总要做个总结

这篇文章是介绍项目中开发的一些经历.

  1. 会首先列出JFinal的Model开发 (只是Model的使用)
  2. 使用Beetlsql替换JFinal的Model (给出一种较爽编码方式)
  3. 对比两个在写多条件查询sql时, (就是需要条件判断, 为null的不参与查询)

1.JFinal的Model开发

JFinal的ORM下面称JFinal的Model

这是Model的新增 修改 删除

jfinal 实体类1

public class Elephant extends Model<Elephant> {
    public static final Elephant Dao = new Elephant();
    public List<Integer> findIds() {
        return Db.query("select id from tb_bird");
    }
}

 

jfinal 测试用例1

@Log4j
public class ElephantTest {
    @Test
    public void testChainSave() {
        // 无需要创建字段
        new Elephant().set("age", 17).set("name", "jfinal").set("desc", "mvc + orm").save();
    }

    @Test
    public void testUpdate() {
        Elephant elephant = Elephant.Dao.findById(1);
        elephant.set("age", 18).update();
    }

    @Test
    public void testDelete() {
        Elephant elephant = Elephant.Dao.findById(1);
        elephant.delete();
    }
}

 

    说实话,刚开始看示例的时候感觉很不错. 毕竟不需要自己定义任何字段就能直接将数据保存到表中.

    刚开始也使用这种方式进行开发, 但是如果有多个地方使用了这样的代码, 并且产品开发过程中需要更改某几个字段,那么小噩梦就开始了. 毕竟没有工具可以进行这种检测. 表字段名直接写成字符串也都是合法的.那么唯一可以做的就只能搜索整个项目使用到这个类的地方, 然后逐个检查而且需要看得很细. 因为这是人工的检查,所以工具并不能帮上你很大忙.

还有一点是采用这种方式开发就是你必须记住每个表的每个字段, 并且不能出错(需要手动在代码中敲击数据库表字段的名字).

如果是几张表并且字段少的情况下这样玩代码也没有什么. 如果是百张表的项目你会工作得很愉快.

所以就约定了实体类代码必须提供getter,setter. 这样做工具可以提供错误检测

于是项目中的实体类就变成了下面的方式

jfinal 实体类2 getter, setter

public class Elephant extends Model<Elephant> {
    public static final Elephant Dao = new Elephant();

    public int getId() {
        return this.getInt("id");
    }

    public Elephant setId(int id) {
        this.set("id", id);
        return this;
    }

    public int getAge() {
        return this.getInt("age");
    }

    public Elephant setAge(int age) {
        this.set("age", age);
        return this;
    }

    public String getName() {
        return this.getStr("name");
    }

    public Elephant setName(String name) {
        this.set("name", name);
        return this;
    }

    public String getDesc() {
        return this.getStr("desc");
    }

    public Elephant setDesc(String desc) {
        this.set("desc", desc);
        return this;
    }

    public List<Integer> findIds() {
        return Db.query("select id from tb_bird");
    }
}

实体类代码提供了getter,setter (为了继续保持链式风格在setter都返回了自身). 这样做工具可以提供错误检测, 看起来这样很不错了.

jfinal 测试用例2

这是添加getter,setter后的测试用例

@Log4j
public class ElephantChainTest {
    @Test
    public void testChainSave() {
        new Elephant().setAge(17).setName("jfinal").setDesc("mvc + orm").save();
    }

    @Test
    public void testUpdate() {
        Elephant elephant = Elephant.Dao.findById(1);
        elephant.setAge(18).update();
    }

    @Test
    public void testDelete() {
        Elephant elephant = Elephant.Dao.findById(1);
        elephant.delete();
    }
}

 

    好了, Model实体类都拥有了getter, setter方法了. 即便表业务需要改一个字段的名字也没什么大碍, 我就改这个实体类对应的方法名就行. 工具会给我们报错在代码的哪一行使用到了这个属性(字段). 并且也不需要使用到这个实体类的地方都手动敲击这个字段名了. 很不错. 方便了很多!

    但是我要告诉你们的是这些getter, setter代码都是人工手动敲上去的. 当初是想用JFinal的Model节省时间的,毫无疑问的是这样做并没有节省我的时间,而且还浪费了很多时间.

这里只是其中一个表对应的实体类(字段也很少, 因为是举例这里只用了4个字段). 如果百个实体类而有的实体类有8个,15个甚至更多属性(是的,你没看错, 你会越来越愉快).

    上面说到为什么这些代码是人工手动敲上去的. 工具不是可以生成getter,setter吗. 我想说的的确是可以生成, 但是工具生成这些代码是有前提条件的. 这些条件就是你必须显示的拥有类的成员属性.

所以很遗憾, 你享受不到工具带来的便捷

    好了, 代码约定Model实体类都必须提供getter, setter方法. 那么代码总要加上点注释吧. 于是代码就变成如下:

public class Elephant extends Model<Elephant> {
    public static final Elephant Dao = new Elephant();

    /**
     * @return 年龄
     */
    public int getAge() {
        return this.getInt("age");
    }

    /**
     * @param age 年龄
     * @return this
     */
    public Elephant setAge(int age) {
        this.set("age", age);
        return this;
    }

    /**
     * @return 名字
     */
    public String getName() {
        return this.getStr("name");
    }

    /**
     * @param name 名字
     * @return this
     */
    public Elephant setName(String name) {
        this.set("name", name);
        return this;
    }

    /**
     * @return 描述
     */
    public String getDesc() {
        return this.getStr("desc");
    }

    /**
     * @param desc 描述
     * @return this
     */
    public Elephant setDesc(String desc) {
        this.set("desc", desc);
        return this;
    }

    public List<Integer> findIds() {
        return Db.query("select id from tb_bird");
    }
}

 

    这样从一个最初只有1行代码的实体类, 最后变成了这样. 如果你在看这篇文字, 感觉变成了这样并有没什么. 但是你想知道变成拥有getter, setter加上注释花了多少时间吗? (请读者自行建立一个拥有8个字段的实体玩下).

我在想我为什么要花如此多的时间创建在model上 (我这样安慰我自己的, 这也属于有氧运动吧).

JAVA 极速WEB+ORM框架 JFinal

JFinal 是基于 Java 语言的极速 WEB + ORM 框架,其核心设计目标是开发迅速、代码量少、学习简单、功能强大、轻量级、易扩展、Restful。在拥有Java语言所有优势的同时再拥有ruby、python等动态语言的开发效率!为您节约更多时间,去陪恋人、家人和朋友 ;)

    块引用中的开发迅速与代码量少.没有明确的说是JFianl的代码量少还是使用JFinal开发项目的代码量少. 至少我认为项目中的Model如果显示提供getter,setter代码量就已经巨大且编码效率极慢(仅是JFinal的ORM给我的感觉. 也并不完全是感觉, 毕竟上面的代码示例包含了我想说的话了).

显示提供也不是,不显示提供也不是. 两难.

2.使用Beetlsql替换JFinal的Model

beetlsql + java8 + lombok 结合使用,达到代码最简洁效果, 并且可阅读性更强 (下面称为第二种编码方式)

(极简版)实体类1

@Data
@FieldDefaults(level = AccessLevel.PRIVATE)  // 属性默认都是private
@lombok.experimental.Accessors(chain = true) // 链式编程
@Table(name = "tb_bird")                     // 实体类与表映射
public class Bee implements $Sql { // 实现$Sql接口, 可以在对象上直接使用 save, update, delete 方法 (不是必须的)
    int id;
    /** 年龄 */
    Integer age;
    /** 名字 */
    String name;
    /** 描述 */
    String desc;
}

lombok.@Data自动提供getter,setter,toString等方法. 所以并不需要在代码中显示的生成getter,setter方法(方法上都会有注释), 这让实体类更加的干净.

上面的示例中实体类只需要实现接口,而不需要任何继承就能达到想要的效果.(Effective Java 第18条:接口优于抽象类)

测试类

@Log4j
public class BeeChainTest {
    @Test
    public void testChainSave() {
        new Bee().setAge(17).setName("beetl").setDesc("beetlsql + orm").save();
    }

    @Test
    public void testUpdate() {
        Bee bee = Bee.Dao.$.unique(1);
        bee.setAge(18).update();
    }

    @Test
    public void testDelete() {
        Bee bee = Bee.Dao.$.unique(1);
        bee.delete();
    }
}

    可以看出, 就ORM方式而言. 我更倾向于后面这种结合.

  1. 自动拥有getter setter, 在也不需要手工敲击(有氧运动)
  2. 代码更加清晰,简洁, 甚至可以说真正的做到了代码量少. 让你开发更迅速.

    在上面JFinal的Model中和下面一种方式, 如果我们都在两个实体类中在追加1个字段(并加上注释), 第二种方式无疑是最方便的.

    因为JFinal的Model添加一个字段的getter setter并加上注释 需要14行代码, 而第二种方式只需要两行(可以在上面给出的示例中自己数).也就是说实体类每增加一个字段, 你的Model类相比使用第二种方式多出12行代码(7倍代码量, 这还是在你手工敲击getter setter名字完全正确率100%的情况下, getName getNema不出现这种拼写意外).

    所以为了防止这种意外你也许会说我可以在Model里面先定义属性并确定类型, 然后使用工具生成getter setter经过部分改动接着在把这个定义的属性删除掉就行了(这样就不会产生拼写意外, 而且能享受工具生成代码的便捷). 我想说的是你很幽默. 并且我想对你说的是曾经我也这样幽默过.

    说实话假设类有10个字段. 那么一个是140代码量和一个是20行代码量的,你更愿意看见哪一个? 不知道你们是怎么想的,我看140行代码的时候, 我还得在工具里上下翻滚.而第二种方式我一眼就能明白是怎么回事.

如果字段改个名字那么Model需要改动的地方有7处(换句话说就是维护7处). 第二种方式只需要改1处. (这里仅计算实体类里面的改动, 暂不考虑被项目中其他地方引用该实体类的地方)

 

beetlsql的链式编程

有一个坏消息是beetlsql在发布这篇文章的时候还不能支持链式编程

作者( @闲大赋 )在论坛上是这样回复的:

因为setter方法这种写法不是Javabean的标准写法,所以beetl无法认定这是一个属性,因此才会有刚才那种生成的sql语句

beetlsql会在下个版本支持读取field而不是getter,setter,不过需要点时间

当然有坏消息就会有好消息. 细心的朋友会发现将来的版本中作者会提供

 

 

3.对比两个在写多条件查询sql时

    当然,如果仅仅是从上面的Model做对比还是无法吸引你的话,那么这一节中就要拿出Beetlsql锋利的一面了(这里不会列举得很详细,只是简单的几个方法介绍).更具体的得到beetlsql官网上查阅资料.

有多个条件参数(就是需要条件判断, 为null的不参与查询)

下面各举三个方法执行同样的sql查询做比较

  • 查询所有id - findIds
  • 三个条件参数来查询单个对象 - findOne
  • 两个条件参数来查询一个对象列表 - finds

jfinal Model类现在的代码加上这三个查询后的代码

public class Elephant extends Model<Elephant> {
    public static final Elephant Dao = new Elephant();

    /**
     * @return 年龄
     */
    public int getAge() {
        return this.getInt("age");
    }

    /**
     * @param age 年龄
     * @return this
     */
    public Elephant setAge(int age) {
        this.set("age", age);
        return this;
    }

    /**
     * @return 名字
     */
    public String getName() {
        return this.getStr("name");
    }

    /**
     * @param name 名字
     * @return this
     */
    public Elephant setName(String name) {
        this.set("name", name);
        return this;
    }

    /**
     * @return 描述
     */
    public String getDescription() {
        return this.getStr("description");
    }

    /**
     * @param description 描述
     * @return this
     */
    public Elephant setDescription(String description) {
        this.set("description", description);
        return this;
    }

    public List<Integer> findIds() {
        return Db.query("select id from tb_bird");
    }

    public Elephant findOne(int age, String name, String description) {
        StringBuilder sql = new StringBuilder("select * from tb_bird where 1 = 1");
        List<Object> pars = new LinkedList<>();

        if (age != 0) {
            sql.append(" and age = ? ");
            pars.add(age);
        }

        if (name != null) {
            sql.append(" and name = ? ");
            pars.add(name);
        }

        if (description != null) {
            sql.append(" and description = ? ");
            pars.add(description);
        }

        return this.findFirst(sql.toString(), pars.toArray());
    }

    public List<Elephant> finds(int age, String name) {
        StringBuilder sql = new StringBuilder("select * from tb_bird where 1 = 1");
        List<Object> pars = new LinkedList<>();

        if (age != 0) {
            sql.append(" and age = ? ");
            pars.add(age);
        }

        if (name != null) {
            sql.append(" and name = ? ");
            pars.add(name);
        }

        return Db.query(sql.toString(), pars.toArray());
    }
}

 

jfinal Model查询测试用例

@Log4j
public class ElephantFindTest {
    @Before
    public void bf() {
        Config.jfinalInit();
    }

    @Test
    public void findIds() {
        // 查询所有主键
        List<Integer> ids = Elephant.Dao.findIds();
        log.info(ids);
    }

    @Test
    public void findOne() {
        Elephant one = Elephant.Dao.findOne(18, "a", "n");
        log.info(one);
    }

    @Test
    public void finds() {
        List<Elephant> list = Elephant.Dao.finds(18, "b");
        log.info(list);
    }
}

 

 

(极简版) 第二种方式类现在的代码加上这三个查询后的代码

@Data
@FieldDefaults(level = AccessLevel.PRIVATE)  // 属性默认都是private
@lombok.experimental.Accessors(chain = true) // 链式编程
@Table(name = "tb_bird")                     // 实体类与表映射
public class Bee implements $Sql { // 实现$Sql接口, 可以在对象上直接使用 save, update, delete 方法 (不是必须的)
    int id;
    /** 年龄 */
    Integer age;
    /** 名字 */
    String name;
    /** 描述 */
    String description;

    // 自定义sqlDao处理类. (如果没有自定义sql就没必要定义这个接口)
    public interface Dao extends $Mapper<Bee> {
        /** 约定使用$符号表示自己 (这句代码也不是必须的) */
        Dao $ = mapper(Dao.class);

        /**
         * 使用注解忽略默认的内置处理
         * 也可以写成下面的方式, 这里只是示例使用
         * @return 查询表所有主键
         */
        @Ignore
        default List<Integer> findIds() {
            return $().execute(new SQLReady("select id from tb_bird"), Integer.class);
        }

        // 这里无需再写接口实现类, 只需要在bee.md文件写sql
        @SqlStatement(params = "age,name,description")
        Bee findOne(int age, String name, String description);

        // 这里无需再写接口实现类, 只需要在bee.md文件写sql
        @SqlStatement(params = "age,name")
        List<Bee> finds(int age, String name);
    }
}

 

第二种方式查询测试用例

@Log4j
public class BeeFindTest {
    @Before
    public void bf() {
        Config.dbInit();
    }

    @Test
    public void findIds() {
        // 查询所有主键
        List<Integer> ids = Bee.Dao.$.findIds();
        log.info(ids);
    }

    @Test
    public void findOne() {
        Bee one = Bee.Dao.$.findOne(18, "a", "n");
        log.info(one);
    }

    @Test
    public void finds() {
        List<Bee> list = Bee.Dao.$.finds(18, "b");
        log.info(list);
    }
}

    从上面两个示例代码中可以看出优缺点:

试着想一下,假设这类有15个字段并且与数据库交互的方法有10个左右.这里只是这样列举了一个类的三个方法(并且最多的查询条件只有三个,Model需要代码量就太多了点.那么我相信你的项目中有条件参数的并且大于三个的不会没有), 实际项目中表的数量比这个恐怖得多,这个时候就不是假设了,而是玩真的了.

关于Model

    Model把所有与数据库打交道的地方都写在一个类里面. , 你可以想象这个Model的代码有多庞大与臃肿,维护费时

关于beetlsql

    不需要编写数据库打交道的实体类. 只需要定义接口并编写对应 md文件

所有sql语句都存放类对应的md文件中管理 (如果简单的语句可以直接写在接口中, 声明default就可以)

在两个实现同样功能的情况下, 而第二种方式编写代码是极其的简洁, 甚至预览代码都快很多倍.

 

这里在放上第二种方式与md(sql)文件管理的截图

 

最后

上面都是亲自开发在有很多表的项目中的一些总结与讨论.

当然本文并不是说JFinal的Model不好用也不是说jfinal的缺点, 而是找到了一种(这个是仅是自认为)可以替代Model更好的编码方式.

这里是一个 beetlsql demo 大家可以参考.

最后我想说的是,一旦你使用beetlsql并结合上面推荐的方式编码. 你就在也回不去了,因为真的很爽.

曾经使用过 (Hibernate Mybatis) 在使用beetlsql, 你也不会想回到过去

 

 

 

 

共有 人打赏支持
渔泯小镇
粉丝 41
博文 2
码字总数 13369
评论 (97)
闲大赋
支持一下,作者用jfinal2.0完成了一个有数百个表系统后的体验,难得,原jfinal使用者能借鉴,beetlsql 得到了博主赞扬,推荐一下我写的beetlsql
lckao
首先我很佩服用JFinal开发过有上百张表的项目,但感觉你还是没有很熟悉JFinal,JFinal从2.0开始引入IBean,可以自动生成getter,setter,更何况你上面的getter,setter是Lombok在起作用,和beetlsql没什么关系吧;还有多条件查询sql这个问题,不知道你为什么写的这么麻烦,JFinal2.0也有很好的方法啊,更何况现在JFinal3.0有更好的解决方案了。建议可以研究一下JFinal3.0
渔泯小镇

引用来自“lckao”的评论

首先我很佩服用JFinal开发过有上百张表的项目,但感觉你还是没有很熟悉JFinal,JFinal从2.0开始引入IBean,可以自动生成getter,setter,更何况你上面的getter,setter是Lombok在起作用,和beetlsql没什么关系吧;还有多条件查询sql这个问题,不知道你为什么写的这么麻烦,JFinal2.0也有很好的方法啊,更何况现在JFinal3.0有更好的解决方案了。建议可以研究一下JFinal3.0
正因为熟悉才不用IBean
lckao

引用来自“lckao”的评论

首先我很佩服用JFinal开发过有上百张表的项目,但感觉你还是没有很熟悉JFinal,JFinal从2.0开始引入IBean,可以自动生成getter,setter,更何况你上面的getter,setter是Lombok在起作用,和beetlsql没什么关系吧;还有多条件查询sql这个问题,不知道你为什么写的这么麻烦,JFinal2.0也有很好的方法啊,更何况现在JFinal3.0有更好的解决方案了。建议可以研究一下JFinal3.0

引用来自“渔泯小镇”的评论

正因为熟悉才不用IBean
getter,setter是Lombok在起作用,虽然没有用过Lombok,但应该Lombok和JFinal可以结合,等一下看看能不能结合。
子木007
个人最佳实践总结。 赞! 那你的 mvc 部分还是用 jfinal?
渔泯小镇

引用来自“子木007”的评论

个人最佳实践总结。 赞! 那你的 mvc 部分还是用 jfinal?
是的, 本文只说jfinal的orm对开发带来的不便之处.
壹爱
看完。 很爽。
蓝水晶飞机
哈哈哈……mybatis easy to use
12叔
看来楼主用的不太对呀 jfinal 没怎么麻烦
程序猿猴

引用来自“lckao”的评论

首先我很佩服用JFinal开发过有上百张表的项目,但感觉你还是没有很熟悉JFinal,JFinal从2.0开始引入IBean,可以自动生成getter,setter,更何况你上面的getter,setter是Lombok在起作用,和beetlsql没什么关系吧;还有多条件查询sql这个问题,不知道你为什么写的这么麻烦,JFinal2.0也有很好的方法啊,更何况现在JFinal3.0有更好的解决方案了。建议可以研究一下JFinal3.0

引用来自“渔泯小镇”的评论

正因为熟悉才不用IBean

引用来自“lckao”的评论

getter,setter是Lombok在起作用,虽然没有用过Lombok,但应该Lombok和JFinal可以结合,等一下看看能不能结合。
然而lombok想在jfinal上起作用都起不来
渔泯小镇

引用来自“12叔”的评论

看来楼主用的不太对呀 jfinal 没怎么麻烦
请举例, 不要说些抽象的
三国lz
昨天看了后想评论,结果23点后就不能评论了。
虽然我觉得jfinal的model其实并没有那么难用,但总体而言还是得给作者一个赞的,而且我也是从之前用jfinal的dao,现在换成了使用beetlsql作dao。
看了下作者的代码,感觉大受启发。我在项目里为了让beetlsql的dao支持方法自定义实现,是直接从beetlsql的源码入手进行扩展,相比使用cglib的动态代理的方式而言,在使用上要稍麻烦一点(需要让dao另行继承一个接口,在这个接口里写需要自定义的方法【实现可用default方法,也可另写class去实现】)。现在我打算学习下cglib,把我自己的扩展在改进下了:smile:
渔泯小镇
这个项目是在年前完成的
用到的有 Netty · jfinal · ibeetl · LMAX · maven
下个月就会新立项目, 对mvc的需求到不是很大(因为我是做手游服务端的).
所以最近在做前期准备,关注了有 nutz·springBoot·ActFramework· beetlsql· t-io·gradle
正在做这几个的demo.
当然会有人认为我是喷子,或者说这是一篇软文. jfinal我从1.x就接触了. beetl也很早就使用了.
https://www.oschina.net/code/snippet_204433_21360
https://www.oschina.net/code/snippet_204433_44008
https://www.oschina.net/code/snippet_204433_44022
https://www.oschina.net/code/snippet_204433_44936
只是在做到beetlsql的demo的时候找到了一种比较爽的编码方式(orm).所以就写了这篇文章.
然而如果让我在来一篇详细的比较我会做得更好.(毕竟这是第一次写博客)
让大家感觉到是软文的原因是因为这里只列举了beetlsql的原因.
其实有几年编程经验的会想得到也可以用mybatis替换,但我不可能把所有orm都来演示一遍吧.
这里我需要提一下 #ActFramework# .act作者心态很好,能接受大伙对act的看法(并且很实事求是).
渔泯小镇

引用来自“三国lz”的评论

昨天看了后想评论,结果23点后就不能评论了。
虽然我觉得jfinal的model其实并没有那么难用,但总体而言还是得给作者一个赞的,而且我也是从之前用jfinal的dao,现在换成了使用beetlsql作dao。
看了下作者的代码,感觉大受启发。我在项目里为了让beetlsql的dao支持方法自定义实现,是直接从beetlsql的源码入手进行扩展,相比使用cglib的动态代理的方式而言,在使用上要稍麻烦一点(需要让dao另行继承一个接口,在这个接口里写需要自定义的方法【实现可用default方法,也可另写class去实现】)。现在我打算学习下cglib,把我自己的扩展在改进下了:smile:
是的, 有情况才会研究新玩法 :smirk:
测试基准域
支持一下。之前用jfinalUIB的时候直接把字段写bean里面了,然后用eclipse自动生成的getter setter,不过还是麻烦了些。
开源中国首席大弟子
你用的版本比较低了吧 jfinal2.0开始 model可以自动生成getter和setter 3.0开始推出模版引擎 使用模版引擎处理带各种条件的sql更加便捷
渔泯小镇

引用来自“开源中国首席大弟子”的评论

你用的版本比较低了吧 jfinal2.0开始 model可以自动生成getter和setter 3.0开始推出模版引擎 使用模版引擎处理带各种条件的sql更加便捷
你也许以为我总结的是开发demo, 而不是项目. 说实话,开发demo怎么切换版本是没有任何隐患的.
为什么不升级到3.0! 就连JDK向下兼容的都难保证大家的是最新的,更何况3.0API有改动.
一个线上产品, 而且是跳大版本的,你觉得有线上产品会随意升级吗.
要么你是只用JFinal做过小demo, 要么是你没认真看文章.
CherryDSL
貌似Jfinal3.0已结可以文件管理sql 了,而且代码了也精简了吧..你这个不是3.0?
跟猪谈理想
博主的这个 beetlsql + java8 + lombok 是一种做法, 但是你说Jfinal 麻烦这个有点不认同。Jfinal2.0 自带了生成bean的代码,你只需要配置好数据连接和生成代码的存放路径, 运行一下就生成了。如果要加字段,你只需要在数据库加好,然后重新运行生成的代码,就会生成新的bean。开发过程中, 只要你改了数据库,只需要重新运行一下生成的代码,而且也完全不用去手动修改bean的任何代码。
渔泯小镇

引用来自“CherryDSL”的评论

貌似Jfinal3.0已结可以文件管理sql 了,而且代码了也精简了吧..你这个不是3.0?
你也许以为我总结的是开发demo, 而不是项目. 说实话,开发demo怎么切换版本是没有任何隐患的.
为什么不升级到3.0! 就连JDK向下兼容的都难保证大家的是最新的,更何况3.0API有改动.
一个线上产品, 而且是跳大版本的,你觉得有线上产品会随意升级吗.
要么你是只用JFinal做过小demo, 要么是你没认真看文章.
×
渔泯小镇
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: