Freemarker 实战: 代码生成器
在上一节介绍了 Freemarker的一些基础的用法, 那么在这一节呢, 通过简易代码生成器来实际感受下Freemarker在开发中的魅力
这里只是生成MySQL的代码,其他数据库的话可以自行扩展
开发环境
Maven管理, Freemarker + MyBatis 整合开发
生成的代码架构:SSM框架
Maven 配置
x_utils
com.sanq.product.x_utils
1.0-SNAPSHOT
com.sanq.product.x_utils
util_common
1.0-SNAPSHOT
com.sanq.product.x_utils
util_redis
1.0-SNAPSHOT
junit
junit
3.8.1
org.mybatis
mybatis
mysql
mysql-connector-java
runtime
org.freemarker
freemarker
ch.qos.logback
logback-classic
generate
src/main/java
**/*.properties
**/*.xml
**/*.ftl
false
src/main/resources
**/*.properties
**/*.xml
**/*.ftl
false
关于配置中 x_utils, 大家可以查看代码: 代码查看,
题外话
X_Util模块描述
- Common 对开发中经常使用的一些工具类进行封装
- Redis 对Redis的操作进行封装
- GenerateCode 代码生成器的源代码(早期开发, 只是修改ftl文件,没有修改FreemarkerUtil,可以参考)
pom.xml配置完成
Mybatis 配置
在config.properties
中配置数据源等
# 数据源的配置
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
配置
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
- ?cap_first: 将首字母大写
- !"" : 如果为null或者不存在该属性 就显示空字符串
if
- 判断当前list不为null and list.size() > 0
list
- 循环迭代
mybatis.ftl
这里和上面是一样的,不过需要注意的是 ftl文件中如果有包含 #
这种特殊字符, 需要对其进行转义才会成功输出
${r"#{" + field.javaField + "}"}
到此 ftl文件也已经完成, 下面进行正文
查询所有表的sql 使用这个
SHOW TABLE STATUS
这里我们需要以下字段 property为实体Tables中的属性
下来查询表的字段
SELECT * FROM information_schema.COLUMNS where TABLE_NAME = 'tb_users' AND table_schema = 'test' order by column_key desc;
这里我们需要以下字段 property为实体Fields中的属性
下面是在实体中的操作
Tables.java
public class Tables implements Serializable {
private String name;
private String comment;
private String javaName;
private List 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 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();
}
public List getTables() {
String tableSchema = mDaoSupport.getPropVal("table_schema");
SqlSession session = mDaoSupport.getSession();
/**获取所有的表*/
TableMapper tm = session.getMapper(TableMapper.class);
List tables = tm.findAllTables();
if(tables != null && tables.size() > 0) {
/**获取表中所有的字段*/
FieldMapper fm = session.getMapper(FieldMapper.class);
Map map = null;
for(Tables table : tables) {
map = new HashMap();
map.put("tableSchema", tableSchema);
map.put("tableName", table.getName());
List fields = fm.findFieldByTable(map);
table.setFields(fields);
}
}
return tables;
}
@Test
public void generateCode() {
List tableses = getTables();
generate(tableses);
}
private void generate(List 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代码,余下的时间就可以好好的玩耍啦。。。