在上一节介绍了 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代码,余下的时间就能够好好的玩耍啦。。。