需要生成数据字典,但是网上没有找到满意的(我只从别人那里拿到一个可以生成mysql,html版本的),所以花点时间自己写了一个简易的docx版本,这里分享一下,希望有需要的可以节约点时间;
使用Spring3.1,DataSouce采用C3p0,jdk用的是1.6,数据库采用的是mysql。
思路
思路就是:
1.利用sql语句取出指定数据库中的表注释以及相应的表信息
2.将这些信息,插入PO生成I的表格中(不存在的信息用"-"代替),over。
主要涉及的三条sql语句:
取出目标数据库的全部表名(可以根据需求进行过滤):
select table_name from information_schema.Tables where table_schema=?
取出指定表的所有字段信息(可以根据需求改动):
select column_name, column_type, is_nullable, column_comment, column_key from information_schema.COLUMNS where table_schema=? and table_name=?
根据表名取出对应的表注释
select table_name, table_comment from information_schema.TABLES where table_schema=?
配置文件有四个:maven配置文件,spring配置文件,jdbc连接用的,日志log4j
包分了5层,感觉有点强迫症,每次写都要分
bean->ColumnInfo:自定义和数据库字段名对应的描述信息,用于表头字段的显示;
bean->ColumnNameEnum:定义查询所需的字段名,搞这个当时是想定义好字段名直接用,不会到时打错字(我当时另外一个想法是定义在properties文件里面);
bean->DataDirectory:数据字典描述类。包括生成docx文件的基础路径,数据库名,ColumnInfo数据集等
剩下几个包就是正常定义。。。dao,service等
效果图
源码在下面,用的时候部署maven,改个数据连接和main中的名字就可以直接运行了
https://github.com/xck503c/DataDirectory
源码
项目目录结构
配置文件
maven配置(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>xck</artifactId>
<groupId>com.xck</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dataDirectoryUtil</artifactId>
<groupId>com.xck</groupId>
<version>1.0.0</version>
<properties>
<java-version>1.6</java-version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<org.springframework.version>4.2.5.RELEASE</org.springframework.version>
</properties>
<dependencies>
<!-- JDBC Database driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10</version>
</dependency>
<!-- JDBC Database Pool -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!-- log -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- spring framework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!-- use docx jar,poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
<version>1.3</version>
</dependency>
</dependencies>
</project>
日志配置(log4j.xml):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration>
<!-- console info -->
<appender name="consoleAppender" class="org.apache.log4j.ConsoleAppender">
<param name="Threshold" value="INFO"/> <!--DEBUG-->
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %p - %m%n" />
</layout>
</appender>
<!-- file info appender -->
<appender name="fileAppenderInfo" class="org.apache.log4j.DailyRollingFileAppender">
<param name="Threshold" value="INFO"/> <!--DEBUG-->
<param name="File" value="./logs/dataDirectory/info.log" />
<param name="Append" value="true" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %p - %m%n" />
</layout>
</appender>
<!-- file error appender -->
<appender name="fileAppenderError" class="org.apache.log4j.DailyRollingFileAppender">
<param name="Threshold" value="ERROR"/>
<param name="File" value="./logs/dataDirectory/error.log" />
<param name="Append" value="true" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %p - %m%n" />
</layout>
</appender>
<logger name="com.xck" additivity="false">
<level value="INFO" /> <!--DEBUG-->
<appender-ref ref="fileAppenderInfo" />
<appender-ref ref="consoleAppender" />
</logger>
<root>
<level value="ERROR" />
<appender-ref ref="fileAppenderError" />
</root>
</log4j:configuration>
数据库连接配置文件(jdbc.properties)
####################################################
db.type=mysql
db.url=jdbc:mysql://127.0.0.1:3306/sqlbook?useUnicode=true&characterEncoding=utf8
db.driverclass=com.mysql.jdbc.Driver
db.username=root
db.password=root
db.maxActive=15
db.maxIdle=10
db.maxWait=3000
db.minIdle=5
####################################################
spring配置文件(applicationContext.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<context:annotation-config />
<context:component-scan base-package="com.xck" />
<context:property-placeholder location="classpath*:jdbc.properties" />
<!-- C3P0 DataSource -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${db.driverclass}" />
<property name="jdbcUrl" value="${db.url}" />
<property name="user" value="${db.username}" />
<property name="password" value="${db.password}" />
<!-- 连接池中保留的最大连接连接数 Default:15 -->
<property name="maxPoolSize" value="${db.maxActive}" />
<!-- 连接池中保留的最小连接数 -->
<property name="minPoolSize" value="${db.minIdle}" />
<!-- 初始化时获取的连接数,取值应该在minPoolSize和maxPoolSize之间 Default:3 -->
<property name="initialPoolSize" value="${db.minIdle}" />
<!-- 最大空闲时间,60s内未使用则连接被丢弃,若为0则永不丢弃 Default:0 -->
<property name="maxIdleTime" value="60" />
<!-- 定义在从数据库中获取新连接失败后重复尝试的次数 Default:30 -->
<property name="acquireRetryAttempts" value="30" />
<!-- 获取连接失败将会引起所有在等待连接池中获取连接的线程抛出异常。但是数据源仍让有效保留,并在下次调用
getConnection()的试试继续尝试获取连接。如果设置为true,那么在尝试获取连接失败后该数据源将申明
已断开并永久关闭 Default:false-->
<property name="breakAfterAcquireFailure" value="false" />
<!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数 Default:3 -->
<property name="acquireIncrement" value="3" />
<!-- 每60s检查所有连接池中的空闲连接数 Default:0 -->
<property name="idleConnectionTestPeriod" value="60" />
<!-- 因性能消耗大,那么请在需要的时候才使用。如果设为true,那么每个connection提交的时候都将校验其
有效性。建议使用idleConnectionTestPeriod或automaticTestTable等方法来提高性能-->
<property name="testConnectionOnCheckout" value="true" />
<property name="automaticTestTable" value="c3p0Test" />
</bean>
</beans>
实体类
全部放在一个地方展示省略一部分代码。
/**
* class desc:自定义和数据库字段名对应的描述信息,用于表头显示
*
* create time:2019-2-21 00:00:00
* @version 1.0.0
*/
public class ColumnInfo {
private String desc; //描述
private int widthPercent; //宽度占比以0~100的数字表示
//...
}
/**
* class desc:主要用于限定数据库的字段名
*
* create time:2019-2-21 00:00:00
* @version 1.0.0
*/
public enum ColumnNameEnum {
COLUMN_NAME("column_name"), COLUMN_TYPE("column_type"), IS_NULLABLE("is_nullable"),
COLUMN_COMMENT("column_comment"), COLUMN_KEY("column_key");
private String name;
private ColumnNameEnum(String name){
this.name = name;
}
}
/**
* class desc:用于描述所需创建数据字典的基础信息
*
* create time:2019-2-21 00:00:00
* @version 1.0.0
*/
public class DataDirectory {
private String baseFielPath; //基础路径不包括文件名,文件名固定
private String columnSql;
//数据字典信息
private String databaseName; //目标数据库名
private LinkedHashMap<ColumnNameEnum, ColumnInfo> tableHeader; //表头名和表名中文名以及宽度
public String getBaseFielPath() {
return baseFielPath;
}
public void setBaseFielPath(String baseFielPath) {
if(StringUtils.isNotBlank(baseFielPath)){
this.baseFielPath = baseFielPath;
return;
}
this.baseFielPath = "." + File.pathSeparator;
}
//...
public void setTableHeader(LinkedHashMap<ColumnNameEnum, ColumnInfo> tableHeader) {
Iterator iterator = tableHeader.entrySet().iterator();
int percent = 0;
StringBuffer sb = new StringBuffer("");
while(iterator.hasNext()){
Map.Entry entry = (Map.Entry)iterator.next();
ColumnInfo columnInfo = (ColumnInfo)entry.getValue();
int widthPercent =columnInfo.getWidthPercent();
//遍历检查描述是否为空,或百分比数值是否正确
if(StringUtils.isBlank(columnInfo.getDesc()) || widthPercent<=0 || widthPercent>=100){
iterator.remove(); //移除
}
percent += widthPercent;
sb.append(entry.getKey()).append(",");
}
this.columnSql = sb.delete(sb.length()-1, sb.length()).toString();
if(percent > 100){
this.tableHeader = new LinkedHashMap<ColumnNameEnum, ColumnInfo>();
return;
}
this.tableHeader = tableHeader;
}
}
DAO(DataDirecory.java)
package com.xck.dao;
import com.xck.bean.ColumnNameEnum;
import com.xck.bean.DataDirectory;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
@Repository("mysql_dao")
public class DataDirectoryDAO {
private static Logger log = Logger.getLogger(DataDirectoryDAO.class);
@Resource(name = "dataSource")
public DataSource dataSource;
/**
* 获取表字段信息
*
* @param dataDirectory
* @return
*/
public Map<String, List<String>> getTableColumnsInfo(DataDirectory dataDirectory) {
Map<String, List<String>> map = new LinkedHashMap<String, List<String>>();
String databaseName = dataDirectory.getDatabaseName();
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
String sql = "select table_name from information_schema.Tables where table_schema=?";
try {
conn = dataSource.getConnection();
ps = conn.prepareStatement(sql);
ps.setString(1, databaseName);
rs = ps.executeQuery();
while (rs.next()) {
String table_name = rs.getString("table_name");
map.put(table_name, getColumnsInfo(dataDirectory, table_name));
}
} catch (SQLException e) {
log.error("get connection error!", e);
} finally {
freeConnection(ps, conn);
}
return map;
}
private List<String> getColumnsInfo(DataDirectory dataDirectory, String table_name) {
List<String> list = new ArrayList<String>();
String databaseName = dataDirectory.getDatabaseName();
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
String sql = "select " + dataDirectory.getColumnSql() + " from information_schema.COLUMNS where table_schema=? and table_name=?";
try {
conn = dataSource.getConnection();
ps = conn.prepareStatement(sql);
ps.setString(1, databaseName);
ps.setString(2, table_name);
rs = ps.executeQuery();
while (rs.next()) {
StringBuffer sb = new StringBuffer("");
Iterator<ColumnNameEnum> iterator = dataDirectory.getTableHeader().keySet().iterator();
while(iterator.hasNext()){
sb.append(isEmptyReplaceStr(rs.getString(iterator.next().name()), "-")).append("@");
}
list.add(sb.delete(sb.length()-1, sb.length()).toString());
}
} catch (SQLException e) {
log.error("get connection error!", e);
} finally {
freeConnection(ps, conn);
}
return list;
}
/**
* 获取表的注释
*
* @param databaeName
* @return
*/
public Map<String, String> getTableComment(String databaeName) {
Map<String, String> tableCommentMap = new HashMap<String, String>();
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
String sql = "select table_name, table_comment " +
"from information_schema.TABLES where table_schema=?";
try {
conn = dataSource.getConnection();
ps = conn.prepareStatement(sql);
ps.setString(1, databaeName);
rs = ps.executeQuery();
while (rs.next()) {
String table_name = isEmptyReplaceStr(rs.getString("table_name"), "");
String table_comment = isEmptyReplaceStr(rs.getString("table_comment"), "");
tableCommentMap.put(table_name, table_comment);
}
} catch (SQLException e) {
log.error("get connection error!", e);
} finally {
freeConnection(ps, conn);
}
return tableCommentMap;
}
private String isEmptyReplaceStr(String str, String replaceStr) {
if (StringUtils.isEmpty(str)) {
return replaceStr;
}
return str;
}
private void freeConnection(PreparedStatement ps, Connection conn) {
try {
if (ps != null) {
ps.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
log.error("close connection error!", e);
}
}
}
Service(DataDirectory.java)
package com.xck.service;
import com.xck.bean.ColumnInfo;
import com.xck.bean.DataDirectory;
import com.xck.dao.DataDirectoryDAO;
import com.xck.util.POIWordUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.poi.xwpf.usermodel.*;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@Service
public class DataDirectoryService {
private static Logger log = Logger.getLogger(DataDirectoryService.class);
@Resource
DataDirectoryDAO dataDirectoryDAO;
/**
* 生成word版本数据字典的调用入口
*
* @param dataDirectory
*/
public void generateDataDirectory(DataDirectory dataDirectory){
if(dataDirectory.getTableHeader().size() == 0){
return;
}
Map<String, String> tableCommentMap = dataDirectoryDAO.getTableComment(dataDirectory.getDatabaseName());
Map<String, List<String>> tableColumnsMap = dataDirectoryDAO.getTableColumnsInfo(dataDirectory);
generatedoc(dataDirectory, tableColumnsMap, tableCommentMap);
}
private void generatedoc(DataDirectory dataDirectory, Map<String, List<String>> tableMap, Map<String, String> tableCommentMap) {
String databaseName = dataDirectory.getDatabaseName();
String filePath = dataDirectory.getBaseFielPath() + databaseName + "数据字典.docx";
OutputStream out = null;
try {
XWPFDocument doc = new XWPFDocument();
//设置标题
POIWordUtil.setTitle(doc, databaseName + "数据字典", 20, ParagraphAlignment.CENTER);
POIWordUtil.nextLine(doc, 3); //换行三次
Iterator iterator = tableMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
String table_name = (String) entry.getKey();
List<String> columnList = (List<String>) entry.getValue();
String tableNameStr = table_name;
if (StringUtils.isNotEmpty(tableCommentMap.get(table_name))) {
tableNameStr += "【" + tableCommentMap.get(table_name) + "】";
}
POIWordUtil.newParagraphText(doc, tableNameStr); //插入表名
//创建指定行列的表格
XWPFTable table = POIWordUtil.createTable(doc, columnList.size() + 1, dataDirectory.getTableHeader().size());
//设置表头,列宽
XWPFTableRow rowHeader = table.getRow(0);
Iterator headeriterator = dataDirectory.getTableHeader().entrySet().iterator();
for(int i=0; headeriterator.hasNext(); i++){
Map.Entry headerEntry = (Map.Entry)headeriterator.next();
ColumnInfo columnInfo = (ColumnInfo)headerEntry.getValue();
String width = (int)(8000*((double)columnInfo.getWidthPercent()/100))+"";
setTableCell(rowHeader, i, columnInfo.getDesc(), width);
}
//插入单元格文本
for (int i = 0; i < columnList.size(); i++) {
String[] columninfos = columnList.get(i).split("@");
XWPFTableRow row = table.getRow(i + 1);
for (int j = 0; j < columninfos.length; j++) {
setTableCell(row, j, columninfos[j], "0");
}
}
POIWordUtil.nextLine(doc, 1);
}
out = new FileOutputStream(new File(filePath));
doc.write(out);
} catch (Exception e) {
log.error("", e);
}
}
public static void setTableCell(XWPFTableRow row, int CellPos, String text, String width) {
XWPFTableCell cell = row.getCell(CellPos);
POIWordUtil.setTableCellTextCenter(cell);
if (!"0".equals(width)) {
POIWordUtil.setTableCellWidth(cell, width);
}
cell.setText(text);
}
}
Util(POIWordUtil.java)
package com.xck.util;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import java.math.BigInteger;
public class POIWordUtil {
/**
* 换行设置
*
* @param doc
* @param times 换几行
*/
public static void nextLine(XWPFDocument doc, int times) {
for (int i = 0; i < times; i++) {
XWPFParagraph nextLine = doc.createParagraph();
nextLine.createRun().setText("\r");
}
}
/**
* 设置标题
* @param doc
* @param text
* @param fontSize
* @param align
*/
public static void setTitle(XWPFDocument doc, String text, int fontSize, ParagraphAlignment align){
XWPFParagraph titleParagraph = doc.createParagraph();
titleParagraph.setAlignment(align);
XWPFRun titleRun = titleParagraph.createRun();
titleRun.setText(text);
titleRun.setFontSize(fontSize);
}
/**
* 新开一个段落,插入文本
* @param doc
* @param text
*/
public static void newParagraphText(XWPFDocument doc, String text){
XWPFParagraph xwpfParagraph = doc.createParagraph();
xwpfParagraph.createRun().setText(text);
}
public static XWPFTable createTable(XWPFDocument doc, int rows, int cols){
XWPFTable table = doc.createTable(rows, cols);
CTTblPr tablePr = table.getCTTbl().getTblPr();
tablePr.getTblW().setType(STTblWidth.DXA);
tablePr.getTblW().setW(new BigInteger("8000"));
return table;
}
/**
* 设置表格中单元格的宽度
*
* @param cell
* @param width
*/
public static void setTableCellWidth(XWPFTableCell cell, String width) {
CTTc cttc = cell.getCTTc();
CTTblWidth ctTblWidth = cttc.addNewTcPr().addNewTcW();
ctTblWidth.setType(STTblWidth.DXA);
ctTblWidth.setW(new BigInteger(width));
}
/**
* 设置单元格文本居中
*
* @param cell
*/
public static void setTableCellTextCenter(XWPFTableCell cell) {
CTTc cttc = cell.getCTTc();
CTTcPr ctPr = cttc.addNewTcPr();
ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
cttc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
}
}
Main(测试)
package com.xck.main;
import com.xck.bean.ColumnInfo;
import com.xck.bean.ColumnNameEnum;
import com.xck.bean.DataDirectory;
import com.xck.service.DataDirectoryService;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.LinkedHashMap;
public class DataDirectoryGenerateMain {
public static void main(String[] args){
AbstractApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
DataDirectoryService service = context.getBean(DataDirectoryService.class);
DataDirectory dataDirectory = new DataDirectory();
dataDirectory.setBaseFielPath("D:\\");
dataDirectory.setDatabaseName("sqlbook");
LinkedHashMap<ColumnNameEnum, ColumnInfo> columnInfoMap = new LinkedHashMap<ColumnNameEnum, ColumnInfo>();
columnInfoMap.put(ColumnNameEnum.COLUMN_NAME, new ColumnInfo("字段名", 20));
columnInfoMap.put(ColumnNameEnum.COLUMN_TYPE, new ColumnInfo("数据类型", 20));
columnInfoMap.put(ColumnNameEnum.IS_NULLABLE, new ColumnInfo("允许为空", 20));
columnInfoMap.put(ColumnNameEnum.COLUMN_COMMENT, new ColumnInfo("字段说明", 30));
columnInfoMap.put(ColumnNameEnum.COLUMN_KEY, new ColumnInfo("键", 10));
dataDirectory.setTableHeader(columnInfoMap);
service.generateDataDirectory(dataDirectory);
}
}