在上一節介紹了 Freemarker的一些基礎的用法, 那麼在這一節呢, 經過簡易代碼生成器來實際感覺下Freemarker在開發中的魅力java
這裏只是生成MySQL的代碼,其餘數據庫的話能夠自行擴展mysql
Maven管理, Freemarker + MyBatis 整合開發git
生成的代碼架構:SSM框架redis
Maven 配置spring
<parent>
<artifactId>x_utils</artifactId>
<groupId>com.sanq.product.x_utils</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<!--依賴-->
<dependencies>
<dependency>
<groupId>com.sanq.product.x_utils</groupId>
<artifactId>util_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.sanq.product.x_utils</groupId>
<artifactId>util_redis</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId> logback-classic</artifactId>
</dependency>
</dependencies>
<build>
<finalName>generate</finalName>
<!--這裏配置是爲了在打包的時候 這些文件不會被忽略-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.ftl</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.ftl</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
複製代碼
關於配置中 x_utils, 你們能夠查看代碼: 代碼查看,sql
題外話數據庫
X_Util模塊描述安全
- Common 對開發中常常使用的一些工具類進行封裝
- Redis 對Redis的操做進行封裝
- GenerateCode 代碼生成器的源代碼(早期開發, 只是修改ftl文件,沒有修改FreemarkerUtil,能夠參考)
pom.xml配置完成bash
在config.properties
中配置數據源等session
# 數據源的配置
driver = com.mysql.jdbc.Driver
url = jdbc:mysql:///test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
username = root
password = root
#table_schema
# 由於在查詢表的時候須要提供庫名, 因此在這裏配置
table_schema = test
# 生成的文件包名
packageName = com.sanq.product.freemarker
複製代碼
mybatis.xml
配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--加載config文件-->
<properties resource="config.properties"/>
<!--別名-->
<typeAliases>
<typeAlias alias="tables" type="com.sanq.product.generate.entity.Tables" />
<typeAlias alias="fields" type="com.sanq.product.generate.entity.Fields" />
</typeAliases>
<!--數據源-->
<environments default="development">
<environment id="development">
<transactionManager type="jdbc"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments>
<!--mapper映射配置--->
<mappers>
<mapper resource="com/sanq/product/generate/mappers/TableMapper.xml" />
<mapper resource="com/sanq/product/generate/mappers/FieldMapper.xml" />
</mappers>
</configuration>
複製代碼
mybatis.xml配置完成
由於開發環境中沒有和Spring整合, 因此在使用的時候就不能經過Spring來管理, 須要寫個工具類來獲取SqlSession
工具類咱們採用單例模式來實現, 單例模式的雙重校驗鎖
,靜態內部類
和枚舉
都是線程安全, 推薦使用
這裏採用雙重校驗鎖
的形式
DaoSupport.java
public class DaoSupport {
private DaoSupport() {}
private static DaoSupport instance;
public static DaoSupport getInstance() {
if(null == instance) {
synchronized (DaoSupport.class) {
if(null == instance)
instance = new DaoSupport();
}
}
return instance;
}
private static final String RESOURCE = "mybatis.xml"; //配置文件
//這裏是獲取SqlSession
public SqlSession getSession(){
SqlSession session = null;
try{
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder() //
.build(Resources.getResourceAsReader(RESOURCE));
session = sessionFactory.openSession();
}catch(Exception ex){
ex.printStackTrace();
}
return session;
}
}
複製代碼
DaoSupport完成
關於FreemarkerUtil這裏再也不給出, 在freemarker入門中已經提供
開發要規範, 在表中字段會存在下劃線 因此在這裏咱們須要把下劃線去掉而且將下劃線跟隨的首字母大寫。
這裏使用靜態內部類
StringUtil.java
public class StringUtil {
private StringUtil() {}
public static String getInstance() {
return Holder.INSTANCE;
}
private static class Holder {
public static final StringUtil INSTANCE = new StringUtil();
}
//替換字符串中指定字符,並將緊跟在它後面的字母大寫
public String replaceChar(String strVal, String tag) {
StringBuffer sb = new StringBuffer();
sb.append(strVal.toLowerCase());
int count = sb.indexOf(tag);
while(count!=0){
int num = sb.indexOf(tag,count);
count = num+1;
if(num!=-1){
char ss = sb.charAt(count);
char ia = (char) (ss - 32);
sb.replace(count,count+1,ia+"");
}
}
String ss = sb.toString().replaceAll(tag,"");
return ss;
}
//將首字母大寫
public String firstUpperCase(String strVal) {
StringBuffer sb = new StringBuffer();
if(null != strVal && !"".equals(strVal)) {
sb.append(String.valueOf(strVal.charAt(0)).toUpperCase());
for(int i = 1; i < strVal.length(); i++) {
sb.append(strVal.charAt(i));
}
}
return sb.toString();
}
}
複製代碼
StringUtil.java完成
以上都是工具類, 下面纔是重點
所謂的代碼生成 只不過是偷懶的一種方式, 由於在開發過程當中, CRUD方法一遍遍的寫, 毫無技術性可言, 因此咱們就經過代碼生成器來生成這些方法, 簡化咱們的開發過程, 讓咱們可以更加專一於業務的操做。
不過代碼生成只是其中的一種方式, 也能夠經過BaseService, BaseMapper來封裝。這個就因人而異了。
Freemarker方面, 重點給你們展現下entity.ftl和mapper.ftl, 其餘的都是按照本身的習慣來寫就能夠。
entity.ftl
package ${entityPackage!""};
import java.io.Serializable;
import java.util.*;
import java.math.BigDecimal;
import org.springframework.format.annotation.DateTimeFormat;
public class ${table.javaName?cap_first!""} implements Serializable {
/**
* version: ${table.comment!""}
*----------------------
* author:sanq.Yan
* date:${nowDate?string("yyyy-MM-dd")} <#--這裏是獲取當前時間, 並格式化-->
*/
private static final long serialVersionUID = 1L;
<#if table.fields?? && table.fields?size gt 0>
<#list table.fields as field>
/**${field.columnComment!""}*/
<#if field.javaType == "Date">
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
</#if>
private ${field.javaType!""} ${field.javaField!""};
</#list>
</#if>
<#if table.fields?? && table.fields?size gt 0>
<#list table.fields as field>
public ${field.javaType!""} get${field.javaField?cap_first!""}() {
return ${field.javaField};
}
public void set${field.javaField?cap_first!""}(${field.javaType!""} ${field.javaField!""}) {
this.${field.javaField!""} = ${field.javaField!""};
}
</#list>
</#if>
}
複製代碼
在這裏用到了3個指令
string
if
list
mybatis.ftl
<select id="findById" resultMap="${table.javaName}Map"
parameterType="<#list table.fields as field><#if field.columnKey == "PRI">${field.javaType}</#if></#list>">
SELECT
<include refid="${table.name}_columns"/>
FROM ${table.name} ${table.name?substring(0,1)}
WHERE
<#list table.fields as field>
<#if field.columnKey == "PRI">
${table.name?substring(0,1)}.${field.columnName} = ${r"#{" + field.javaField + "}"} </#if> </#list> LIMIT 1 </select> 複製代碼
這裏和上面是同樣的,不過須要注意的是 ftl文件中若是有包含 #
這種特殊字符, 須要對其進行轉義纔會成功輸出
${r"#{" + field.javaField + "}"} 複製代碼
到此 ftl文件也已經完成, 下面進行正文
查詢全部表的sql 使用這個
SHOW TABLE STATUS
複製代碼
這裏咱們須要如下字段 property爲實體Tables中的屬性
<resultMap type="tables" id="tableEntityMap">
<result column="Name" property="name"/>
<result column="Comment" property="comment"/>
</resultMap>
複製代碼
下來查詢表的字段
SELECT * FROM information_schema.COLUMNS where TABLE_NAME = 'tb_users' AND table_schema = 'test' order by column_key desc;
複製代碼
這裏須要注意 在查詢表字段的時候須要咱們以前在config.properties配置的schema加上, 否則它會查詢出全部庫中表名爲tb_users的字段.
這裏咱們須要如下字段 property爲實體Fields中的屬性
<resultMap type="fields" id="fieldEntityMap">
<result column="table_name" property="tableName"/>
<result column="column_name" property="columnName"/>
<result column="data_type" property="jdbcType"/>
<result column="column_key" property="columnKey"/>
<result column="column_comment" property="columnComment"/>
</resultMap>
複製代碼
下面是在實體中的操做
Tables.java
public class Tables implements Serializable {
private String name;
private String comment;
private String javaName;
private List<Fields> fields;
private StringUtil mStringInstance = StringUtil.getInstance();
//這裏是對錶名進行轉換 換成Java命名規範的名稱
public String getJavaName() {
int i = this.name.indexOf("_");
this.javaName = i > 0 ? mStringInstance.replaceChar(this.name , "_") : this.name;
return this.javaName;
}
//getting and setting
}
複製代碼
Fields.java
public class Fields implements Serializable {
private String tableName;
private String columnName;
private String javaField;
private String jdbcType;
private String javaType;
private String columnKey;
private String columnComment;
private StringUtil mStringInstance = StringUtil.getInstance();
public String getJavaField() {
/** 處理字段中的特殊字符 */
int i = this.columnName.indexOf("_");
this.javaField = i > 0 ? mStringInstance.replaceChar(this.columnName,
"_") : this.columnName;
return this.javaField;
}
public void setJavaField(String javaField) {
this.javaField = javaField;
}
//轉換成java類型
public String getJavaType() {
/** 處理字段類型 */
if (this.jdbcType.equalsIgnoreCase("varchar")
|| this.jdbcType.equalsIgnoreCase("char")
|| this.jdbcType.equalsIgnoreCase("text")
|| this.jdbcType.equalsIgnoreCase("longtext")) {
setJavaType("String");
} else if (this.jdbcType.equalsIgnoreCase("int")
|| this.jdbcType.equalsIgnoreCase("tinyint")
|| this.jdbcType.equalsIgnoreCase("smallint")
|| this.jdbcType.equalsIgnoreCase("mediumint")) {
setJavaType("Integer");
} else if (this.jdbcType.equalsIgnoreCase("date")
|| this.jdbcType.equalsIgnoreCase("time")
|| this.jdbcType.equalsIgnoreCase("datetime")
|| this.jdbcType.equalsIgnoreCase("timestamp")) {
setJavaType("Date");
} else if (this.jdbcType.equalsIgnoreCase("double")) {
setJavaType("Double");
} else if (this.jdbcType.equalsIgnoreCase("long")
|| this.jdbcType.equalsIgnoreCase("bigint")) {
setJavaType("Long");
} else if(this.jdbcType.equalsIgnoreCase("decimal")) {
setJavaType("BigDecimal");
} else if(this.jdbcType.equalsIgnoreCase("float")) {
setJavaType("Float");
}
return javaType;
}
//getting and setting
}
複製代碼
在App.java中 書寫執行程序 推薦使用junit
public class App {
DaoSupport mDaoSupport;
FreemarkerUtil mFreemarkerUtil;
StringUtil mStringUtil;
String mFilePath;
String mPackName;
Map<String,Object> mRoot;
@Before
public void before() {
mDaoSupport = DaoSupport.getInstance();
mFreemarkerUtil = FreemarkerUtil.getInstance();
mStringUtil = StringUtil.getInstance();
mFilePath = PropUtil.getPropVal("packageName").replace(".", File.separator);
mPackName = PropUtil.getPropVal("packageName");
mRoot = new HashMap<String, Object>();
}
public List<Tables> getTables() {
String tableSchema = mDaoSupport.getPropVal("table_schema");
SqlSession session = mDaoSupport.getSession();
/**獲取全部的表*/
TableMapper tm = session.getMapper(TableMapper.class);
List<Tables> tables = tm.findAllTables();
if(tables != null && tables.size() > 0) {
/**獲取表中全部的字段*/
FieldMapper fm = session.getMapper(FieldMapper.class);
Map<String,String> map = null;
for(Tables table : tables) {
map = new HashMap<String,String>();
map.put("tableSchema", tableSchema);
map.put("tableName", table.getName());
List<Fields> fields = fm.findFieldByTable(map);
table.setFields(fields);
}
}
return tables;
}
@Test
public void generateCode() {
List<Tables> tableses = getTables();
generate(tableses);
}
private void generate(List<Tables> tableses) {
//com.sanq.product.freemarker
//劃分文件生成目錄
String controllerPackage = mFilePath + File.separator + "controller";
String entityPackage = mFilePath + File.separator + "entity";
String entityVoPackage = entityPackage + File.separator + "vo";
String servicePackage = mFilePath + File.separator + "service";
String serviceImplPackage = mFilePath + File.separator + "service" + File.separator + "impl";
String mapperPackage = mFilePath + File.separator + "mapper";
//將數據傳遞給Freemarker
mRoot.put("tables", tableses);
mRoot.put("packageName", mPackName.replace(File.separator,"."));
mRoot.put("controllerPackage", controllerPackage.replace(File.separator,"."));
mRoot.put("entityPackage", entityPackage.replace(File.separator,"."));
mRoot.put("entityVoPackage", entityVoPackage.replace(File.separator,"."));
mRoot.put("servicePackage", servicePackage.replace(File.separator,"."));
mRoot.put("serviceImplPackage", serviceImplPackage.replace(File.separator,"."));
mRoot.put("mapperPackage", mapperPackage.replace(File.separator,"."));
mRoot.put("nowDate", new Date());
//這裏生成文件
String tableJavaName;
for(Tables table : tableses) {
tableJavaName = mStringUtil.firstUpperCase(table.getJavaName());
mRoot.put("table", table);
try {
generateFile("java/entity.ftl", entityPackage, tableJavaName + ".java");
generateFile("java/entityVo.ftl", entityVoPackage, tableJavaName + "Vo.java");
generateFile("java/controller.ftl", controllerPackage, tableJavaName + "Controller.java");
generateFile("java/mapper.ftl", mapperPackage, tableJavaName + "Mapper.java");
generateFile("java/mapper_xml.ftl", mapperPackage, tableJavaName + "Mapper.xml");
generateFile("java/service.ftl", servicePackage, tableJavaName + ".Service.java");
generateFile("java/service_impl.ftl", serviceImplPackage, tableJavaName + ".ServiceImpl.java");
} catch (Exception e) {
e.printStackTrace();
}
}
}
//生成文件, 若是文件夾不存在, 建立
public void generateFile(String ftl, String path, String fileName) {
File file = new File("D:"+ File.separator + "tmp" + File.separator + path);
if (!file.exists())
file.mkdirs();
mFreemarkerUtil.out(ftl, mRoot, "D:"+ File.separator + "tmp" + File.separator + path + File.separator + fileName);
}
}
複製代碼
到此, 代碼編寫正式完成
能夠看到代碼已經生成成功
到此, 實戰完成。 內容不是不少。
好好打磨下這個實戰案例,能夠基於本身的習慣生成java代碼,餘下的時間就能夠好好的玩耍啦。。。