JFinal

登录 注册

解决sqlite生成model错误的问题

今天在通过sqlite数据库生成ActiveRecord model的过程中,遇到了一个问题。简单来说,就是生成的model中,所有的属性都是String类型。于是找到了这个问题:http://www.jfinal.com/feedback/261 (JFinal 与 sqlite 3 配合,生成 Model 时有误)。

波总一如既往地回复了这个问题,指出可能是sqlite-jdbc的问题。通过打断点的方式,我也跟踪了一下这个问题,发现了症结所在:

image.png


既然发现了问题,波总也给出了方案,就撸起袖子干吧!

过程中发现sqllite-jdbc对数据库元数据的支持简直是一团***。迫于无奈,只好采用了解析SQL schema的土办法。废话说,上代码:

package tech.nodex.example_springboot_restful.dao.utils;

import com.jfinal.plugin.activerecord.generator.ColumnMeta;
import com.jfinal.plugin.activerecord.generator.MetaBuilder;
import com.jfinal.plugin.activerecord.generator.TableMeta;

import javax.sql.DataSource;
import java.sql.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * 通过解析SQL DML来生成model
 * Created by cz on 2018-3-8.
 */
public class SqliteMetaBuilder extends MetaBuilder {
    Map<String,String> sqlliteTypeMapping;

    public SqliteMetaBuilder(DataSource dataSource) {
        super(dataSource);
        sqlliteTypeMapping = new HashMap<String,String>();
        sqlliteTypeMapping.put("VARCHAR",String.class.getName());
        sqlliteTypeMapping.put("NUMERIC",Integer.class.getName());
        sqlliteTypeMapping.put("BIGINT",Long.class.getName());
        sqlliteTypeMapping.put("INTEGER",Integer.class.getName());
        sqlliteTypeMapping.put("REAL",Double.class.getName());
        sqlliteTypeMapping.put("BLOB","[B");
        //TODO: 补全类型列表
    }


    @Override
    protected void buildColumnMetas(TableMeta tableMeta) throws SQLException {
        PreparedStatement stm = this.conn.prepareStatement("SELECT sql FROM sqlite_master WHERE type = 'table' AND tbl_name = ?");
        stm.setString(1,tableMeta.name);
        ResultSet rs = stm.executeQuery();
        while(rs.next()){
            String dml = rs.getString(1);
            int start = dml.indexOf('(')+1;
            int end = dml.lastIndexOf(')');
            String colsStr = dml.substring(start,end);
            String[] colArray = colsStr.split(",");
            for(String colMetaStr:colArray){
                String[] colMetaParts = colMetaStr.trim().split("[\\s\\(\\)]");
                System.out.println(Arrays.toString(colMetaParts));
                ColumnMeta cm = new ColumnMeta();
                cm.name = colMetaParts[0];
                cm.javaType = sqlliteTypeMapping.get(colMetaParts[1]);
                if(cm.javaType==null){
                    cm.javaType = "java.lang.String";
                }
                cm.attrName = this.buildAttrName(cm.name);
                tableMeta.columnMetas.add(cm);
            }
        }

        rs.close();
        stm.close();
    }
}


在用的时候就简单了:

Generator gernerator = new Generator(dataSource, baseModelPkg, baseModelDir, modelPkg, modelDir);
gernerator.setDialect(new Sqlite3Dialect());
gernerator.setMetaBuilder(new SqliteMetaBuilder(dataSource)); //关键点
gernerator.generate();


以上,问题解决。

对于希望直接拿来即用的同学,有2条善意提醒:

1) SQL schema解析没有经过大量测试,因此不敢保证完全可靠

2) 可能有些类型的映射遗漏了,或写错了,如果出现这种情况,自行修改sqliteTypeMapping中的类型映射即可。


总结

sqlite-jdbc对元数据的实现很糟糕。sqlite中的内置表sqlite_master中包含所有表的create语句。作者被逼无奈,用最笨的办法,实现了sqlite元数据的获取,给遇到同样问题的同学提供一种参考。


评论

  • 03-08 19:44
    jfinal 在查询时有没有类型问题,6 年来反馈查询问题的很少,难道只是生成 model 的时候有问题

    还可以想到一个地方,那就是 TableBuilder 会有问题,这个是用于支持 Controller 的 getBean 与 getModel 的

    感谢你的分享
  • 03-23 17:39
    波老师头像有一丝丝想大V码云
  • 07-24 15:43
    SqliteMetaBuilder 中 cm.name = colMetaParts[0] 要替换 cm.name = colMetaParts[0].replace("\"",""); 不然出现了引号
  • 发送