编写自己的代码生成工具三:代码生成组织者

分类: 开源软件 0人评论 selfly 1年前发布

前面已经解析完了配置文件,有了一切我们想要的信息,接下来就是代码生成了。

假设我们直接去编写代码生成类,有10张表的代码需要生成,那这个类就要负责所有的工作:数据库连接的打开、关闭,表信息的查询,再是代码的生成等等,这样实现起来是不是会很麻烦?我完成了实体类model对象的生成,接下来编写dao生成类的时候,这些通用的操作(数据库连接的打开、关闭等)又得实现一次,另外生成的dao类肯定要依赖前面已经生成好的model实体对象,又怎么得到这个model类的完整类名呢?

基于上面所说,我们需要一个组织者,由它把所有的信息调配起来供内部的生成使用,并完成一些外部的通用操作,不多说了,看源码:

/**
 * 代码生成业务组织者
 * 
 * User: liyd
 * Date: 13-12-6
 * Time: 下午4:01
 */
public class GenerationOrganizer {
    /** 日志对象 */
    private static final Logger LOG = LoggerFactory.getLogger(GenerationOrganizer.class);
    /**
     * 代码生成
     */
    public void codeGenerate() {
        //所有表配置信息
        Map<String, Table> tableMap = EasyCodeContext.getAllTable();
        //所有的任务配置信息
        Map<String, Task> tasks = EasyCodeContext.getAllTask();
        DBUtils.createConnection();
        for (Map.Entry<String, Table> entry : tableMap.entrySet()) {
            Table table = entry.getValue();
            if (CollectionUtils.isEmpty(table.getColumns())) {
                DatabaseProvider provider = DatabaseProviderFactory.buildProvider();
                List<Column> columnList = provider.getTableMetaData(table.getName());
                table.setColumns(columnList);
            }
            //获取包含了所有常量的velocityContext
            VelocityContext context = VelocityUtils.getVelocityContext();
            //表的任务
            Queue<String> tableTasks = table.getTasks();
            String tableTask;
            while ((tableTask = tableTasks.poll()) != null) {
                Task task = tasks.get(tableTask);
                if (task == null) {
                    LOG.info("不存在配置任务{}", tableTask);
                    continue;
                }
                EasyCodeGenerator codeGenerator = task.getClassInstance();
                codeGenerator.doGenerate(table, task, context);
            }
        }
        DBUtils.closeConnection();
    }
}

上面的组织者类主要完成了以下几点操作:

  • 获取所有的表配置信息供生成时使用。
  • 获取所有的任务信息供生成时使用。
  • 打开数据库连接。
  • 构建数据库的扩展实现并查询表的列元数据信息。
  • 建立velocity的上下文,这个上下文中包含了前面定义的所有常量,也就是说你也可以在代码中使用这些常量。另外代码生成任务完成后也会把本次的生成信息放入到这里供后面的生成任务使用。
  • 根据表的任务队列,执行具体的调用生成。这里的任务使用了队列Queue来保存,优点是你可以控制它的生成顺序也就是你在xml中的配置顺序,这在生成dao、service等需要依赖于其它已经生成的如model实体对象时十分有用。
  • 关闭数据库连接。

EasyCodeContext 类源码,这里jdbc信息和常量其实是保存在一个map当中的,只不过加了区分的前缀而已:

/**
 * 配置信息
 *
 * User: liyd
 * Date: 13-11-20
 * Time: 下午4:06
 */
public class EasyCodeContext {
    /** jdbc标识key */
    public static final String              JDBC_KEY       = "jdbc_";
    /** 常量标识key */
    public static final String              CONSTANT_KEY   = "constant_";
    /** 常量map */
    private static Map<String, String>      constantMap    = new HashMap<String, String>();
    /** 表配置map */
    private static Map<String, Table>       tableMap       = new HashMap<String, Table>();
    /** 任务配置 */
    private static Map<String, Task>        taskMap        = new HashMap<String, Task>();
    /** 数据转换map */
    private static Map<String, ConvertType> dataConvertMap = new HashMap<String, ConvertType>();
    /**
     * 添加数据转换配置
     *
     * @param dbType the db type
     * @param convertType the convert type
     */
    public static void addDataConvertType(String dbType, ConvertType convertType) {
        dataConvertMap.put(StringUtils.upperCase(dbType), convertType);
    }
    /**
     * 获取数据转换配置
     * 
     * @param dbType
     * @return
     */
    public static ConvertType getDataConvertType(String dbType) {
        if (StringUtils.isBlank(dbType)) {
            return null;
        }
        return dataConvertMap.get(StringUtils.upperCase(dbType));
    }
    /**
     * 添加任务
     *
     * @param map the task map
     */
    public static void addTask(Map<String, Task> map) {
        taskMap.putAll(map);
    }
    /**
     * 获取所有任务
     * 
     * @return
     */
    public static Map<String, Task> getAllTask() {
        return taskMap;
    }
    /**
     * 获取所有常量
     * 
     * @return
     */
    public static Map<String, String> getAllConstant() {
        return constantMap;
    }
    /**
     * 获取表配置
     * 
     * @return
     */
    public static Map<String, Table> getAllTable() {
        return tableMap;
    }
    /**
     * 添加表配置
     * 
     * @param tableName
     * @param table
     */
    public static void addTable(String tableName, Table table) {
        tableMap.put(tableName, table);
    }
    /**
     * 添加jdbc配置信息
     *
     * @param name
     * @param value
     */
    public static void addJdbcConfig(String name, String value) {
        constantMap.put(JDBC_KEY + name, value);
    }
    /**
     * 获取jdbc配置信息
     * 
     * @param name
     * @return
     */
    public static String getJdbcConfig(String name) {
        return constantMap.get(JDBC_KEY + name);
    }
    /**
     * 添加常量
     *
     * @param map the map
     */
    public static void addConstant(Map<String, String> map) {
        for (Map.Entry<String, String> entry : map.entrySet()) {
            constantMap.put(CONSTANT_KEY + entry.getKey(), entry.getValue());
        }
    }
    /**
     * 获取常量
     * 
     * @param name
     * @return
     */
    public static String getConstant(String name) {
        return constantMap.get(CONSTANT_KEY + name);
    }
}

DBUtils 类源码:

/**
 * 数据库操作辅助类
 *
 * User: liyd
 * Date: 13-12-6
 * Time: 上午10:34
 */
public final class DBUtils {
    /** 日志对象 */
    private static final Logger LOG = LoggerFactory.getLogger(DBUtils.class);
    /** 数据库连接对象 */
    private static Connection   connection;
    /**
     * 创建数据库连接
     */
    public synchronized static void createConnection() {
        try {
            if (connection != null && !connection.isClosed()) {
                return;
            }
            if (EasyCodeContext.getJdbcConfig("driverClassName") == null) {
                return;
            }
            String driverClassName = EasyCodeContext.getJdbcConfig("driverClassName");
            String url = EasyCodeContext.getJdbcConfig("url");
            String username = EasyCodeContext.getJdbcConfig("username");
            String password = EasyCodeContext.getJdbcConfig("password");
            Class.forName(driverClassName);
            connection = DriverManager.getConnection(url, username, password);
        } catch (ClassNotFoundException e) {
            LOG.error("没有找到数据库驱动类,是否添加了数据库驱动的jar包?", e);
            throw new EasyCodeException("没有找到数据库驱动类,是否添加了数据库驱动的jar包?");
        } catch (SQLException e) {
            LOG.error("创建数据库连接出现错误", e);
            throw new EasyCodeException("创建数据库连接出现错误");
        }
    }
    /**
     * 获取配置文件中配置的默认数据库连接
     *
     * @return default connection
     */
    public static Connection getDefaultConnection() {
        try {
            if (connection == null || connection.isClosed()) {
                createConnection();
            }
            return connection;
        } catch (SQLException e) {
            LOG.error("获取数据库连接出现错误", e);
            throw new EasyCodeException("获取数据库连接出现错误");
        }
    }
    /**
     * 关闭数据库连接
     *
     */
    public static void closeConnection() {
        try {
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
        }
    }
}

DatabaseProviderFactory 类源码,这里只是写死了数据库的类型,理想的是可以根据指定的类型返回具体的实现类而不用改动源码,但是在做项目时也就用到了这几个数据库,懒的实现将就用了:

/**
 * 数据库操作者工厂类
 *
 * User: liyd
 * Date: 13-12-6
 * Time: 下午4:20
 */
public class DatabaseProviderFactory {
    /**
     * 获取数据库操作对象
     * 
     * @return
     */
    public static DatabaseProvider buildProvider() {
        String dialect = EasyCodeContext.getJdbcConfig("dialect");
        if (StringUtils.equalsIgnoreCase(dialect, "oracle")) {
            return new OracleProvider();
        } else if (StringUtils.equalsIgnoreCase(dialect, "mysql")) {
            return new MysqlProvider();
        }
        throw new EasyCodeException("没有找到对应的数据库操作类,dialect:" + dialect);
    }
}

上面贴出的几个类都是固定基本不会改动的,下面将讲解具体数据库的查询实现,也就是在DatabaseProviderFactory中构建的DatabaseProvider接口的实现类,待续,,,