需要生成數據字典,但是網上沒有找到滿意的(我只從別人那裏拿到一個可以生成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);
}
}