来到公司的第一个任务是使用公司的框架leap(http://leapframework.org/ ORM层真的非常牛逼)进行项目开发,在这个框架中它规定了自己的javabean的注解(如使用@Id描述主键,用@Column描述字段),从而达到像hibernate中的那种持久化对象的功能,虽然ORM层是非常好用,但是写javabean和注解确实非常麻烦的一件事情(因为不是jpa规范,所有无法使用hibernate-tool)等工具来创建javabean,即使创建了普通的javabean,也要在字段上面写上注解,非常麻烦。那如何写一个工具来帮我们完成这些繁琐的操作呢?
首先需要先对javabean进行抽象
数据库表名->类名
数据库字段->类字段
数据库类型->java类型
数据库主键->需要做标志,使用特殊的注解
//其他需要传入的参数
类的包名
生成的文件地址
有了上述的思路,那么就是说只要有
数据库文件
一套javabean的模版
便能动态的来进行一个数据库到javabean的转换
1.数据库访问
数据库访问方面直接使用JDBC,因为不会出现频率性的数据库访问,所以使用JDBC就够了,在这里我们需要收集的信息是
//数据库里面所有的表
show tables;
//根据表名,获取表里面的所有字段(包括字段类型,字段是否主键等信息)
desc your_table_name;
2.创建模版与模版信息替换
在这里我曾经想过使用正则表达式来解决这个问题,自己定义自己的表达式进行替换,后面在收集想法时,发现一个牛逼的框架 freemarks 可以帮我解决这个问题。
你可以先看文档,也可以参考一下我对这个框架的理解再去看文档,
首先,先说一下freemarks这个框架设计出来的目的,其实他设计出来的目的是为了代替jsp这样的渲染引擎的。
jsp模版渲染引擎使用的思路大概是这样:
变化的值存进request,response,session,jsp引擎解析jsp文件里面的表达式,再去request,response,session取到相关表达的值构建整个页面,再输出到response里面给用户。整个流程都与request,response,session等对象结合在一起。
用过spring mvc 的你应该知道mvc的优点是他在control层用到一个model的对象去帮你封装你要传递给request的值,对用户抽象,让你感觉是
值->model->传递(request..)->jsp->动态页面
但其实底层还是用的request。
那么freemarks的出现就是为了解决这个问题,他提出的两个概念就是“模型”与“模版”。他提出这么一个流程
值->model->传递->模版->动态页面
可见的是它不依赖request等对象,完全封装了一套自己的东西。
freemarks 能不能替代jsp我不能断定,但是和我们的需求是非常切合的。
这个时候你就需要通读一下文档(其实跟El表达式差不多),准备开发。
3.文件存储
使用IO就能完成这个功能,但是我想玩得花一点,就使用了NIO,使用一个内存映射来完成整个功能。
先发一下git地址,以防一些地方讲的不全https://git.oschina.net/jjtHappy/leap-tools
先从下往上讲
首先就是数据库链接,普通的JDBC链接不用介绍,取表名,根据表名取字段的sql已经讲了
主要是如何进行的操作
数据库表名->类名
数据库字段->类字段
数据库类型->java类型
数据库主键->需要做标志,使用特殊的注解
首先,表名转类名,很简单,只是一个格式的转换
/**
*
* Title:把传入的字段转为驼峰
* Description:
* @param table 传入的字段
* @param mod 切割字符
* @return
* @author jjtEatJava
* @date 2017年8月8日
*/
public static String toCamel(String string,String mod) {
String[] tableNameSplit = string.split(mod);
StringBuffer entityName = new StringBuffer();
for(String s : tableNameSplit) {
char[] cs=s.toLowerCase().toCharArray();
cs[0]-=32;
entityName.append(String.valueOf(cs));
}
return entityName.toString();
}
表字段转javabean同理,开头字母转小写就行了
数据库类型转java类型,其实也是一个很简单的操作
/**
* Title:将数据库类型转为java类型
* Description:
* @param tableType 数据库类型
* @return
* @author jjtEatJava
* @date 2017年7月30日
*/
public static String tableTypeToJavaType(String tableType) {
//去括号
int lastIndex = tableType.lastIndexOf("(");
tableType = tableType.substring(0, lastIndex==-1?tableType.length():lastIndex).toLowerCase();
String javaType;
switch(tableType) {
case "char":
case "varchar":
case "tinytext":
case "text":
case "mediumtext":
case "longtext":
javaType="String";
break;
case "tinyblob":
case "blob":
case "mediumblob":
case "longblob":
javaType="byte[]";
break;
case "tinyint":
case "smallint":
case "mediumint":
case "int":
case "bigint":
javaType="Integer";
break;
case "float":
javaType="float";
break;
case "double":
javaType="double";
break;
case "decimal":
javaType="BigDecimal";
break;
case "date":
case "time":
case "year":
case "datetime":
case "timestamp":
javaType="Date";
break;
default:
throw new RuntimeException("无法解析"+tableType+"类型");
}
return javaType;
}
主要你可以了解一下mysql类型与java类型的对照表,写个switch就行了
为了方便,你可以写个javabean,在数据库取出时,就进行一个封装
public class Field {
private String tableField;//数据库字段名
private String tableType;//数据库类型
private String tableKey;//索引类型
private String javaField;//java字段名
private String javaType;//java类型
....
}
这里如何判断是否是主键,只要判断索引类型有没有带“PRI”字段即可
封装一个好用的dao层是对上层是非常方便的,具体代码可以查看SimpleDao
解决了数据获取问题,接下来就是解决模版问题,这个就是看你对javabean的抽象了,当你看完文档后你可以看看我写的模版(结合了框架的一些内容)
package ${packageName}.bean;
<#list importPacks as importPack>
import ${importPack};
#list>
import leap.orm.annotation.Column;
import leap.orm.annotation.Id;
import leap.orm.annotation.Table;
import leap.orm.model.Model;
@Table("${tableName}")
public class ${entityName} extends Model implements java.io.Serializable {
<#-- 构造属性 -->
<#list fields as field>
<#if field.tableKey?contains("PRI")>
@Id
#if>
@Column(name="${field.tableField}")
private ${field.javaType} ${field.javaField?uncap_first};
#list>
<#-- 构造set and get -->
<#list fields as field >
<#-- set -->
public void set${field.javaField?cap_first}(${field.javaType} ${field.javaField?uncap_first}){
this.${field.javaField?uncap_first}=${field.javaField?uncap_first};
}
<#-- get -->
public ${field.javaType} get${field.javaField?cap_first}(){
return this.${field.javaField?uncap_first};
}
#list>
}
可以看模版非常简单,要求的数据模型只是
包名
导入的包
数据库表名
实体名(就是表名的驼峰转化)
上述封装的域列表(用freemarks表达式进行遍历)
这里需要注意的点是,如何决定导入的包,所以你需要考虑几个点
1.根据java类型寻找包,并获取包名
2.包名不能重复
所以有了这个想法,可以通过下面的方法实现
/**
*
* Title:根据所有的域获取导包列表
* Description:
* @param fields bean里面的所有的域
* @return
* @author jjtEatJava
* @date 2017年8月8日
*/
public static Set getPackByFields(List fields) {
Set set = new HashSet();
for(Field field:fields) {
String javaType = field.getJavaType();
String packageName=null;
switch(javaType) {
case "String":
packageName="java.lang.String";
break;
case "Integer":
packageName="java.lang.Integer";
break;
case "Double":
packageName="java.lang.Double";
break;
case "Float":
packageName="";
break;
case "byte[]":
packageName="";
break;
case "Date":
packageName="java.util.Date";
break;
case "BigDecimal":
packageName="java.math.BigDecimal";
break;
default :
packageName="";
}
set.add(packageName);
}
return set;
}
完成了模版设计,就是模型的构建与传递了,传递过程都是按着freemarks的来,再使用NIO进行文件创建
主要关键的方法如下:
/**
*
* Title:创建文件
* Description:
* @param temp 模版
* @param model 数据模型
* @param basePah 基础路径
* @param fileName 文件名
* @throws TemplateException
* @throws IOException
* @throws FileNotFoundException
* @author jjtEatJava
* @date 2017年8月8日
*/
private void createFile(Template temp, Map model,String basePah, String fileName)
throws TemplateException, IOException, FileNotFoundException {
//创建文件夹
File baseDir = new File(basePah);
if(!baseDir.exists())
baseDir.mkdirs();
File file =new File (baseDir,fileName);
if(file.exists()) {
file.delete();
file.createNewFile();
}else {
file.createNewFile();
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer writer = new OutputStreamWriter(out);
//把模版加载到内存
temp.process(model,writer);
//获取缓存字符数组
byte[] byteBuff = out.toByteArray();
RandomAccessFile fileOutput= new RandomAccessFile(file,"rw");
FileChannel chn =fileOutput.getChannel();
//隧道 映射
MappedByteBuffer buff = chn.map(FileChannel.MapMode.READ_WRITE, 0,byteBuff.length);
for(int i=0;i
详细可参照代码。
这样的一个思路便可以让你完成整个dao层的创建
按着这个思路你可以完成整个service层的创建
以下是我写的工具的生成结果
- H:\test4\leap
- bean
- LeapPost.java
- LeapUser.java
- LeapUserPost.java
- service
- BaseService.java
- impl
- BaseServiceImpl.java
- LeapPostServiceImpl.java
- LeapUserPostServiceImpl.java
- LeapUserServiceImpl.java
- LeapPostService.java
- LeapUserPostService.java
- LeapUserService.java
你说这篇文章给不了你干货,但真正的干货我就不信能在一篇文章中看出来的。老老实实看文档,根据本文给出的思路,一步一步才能创建自己的一个基础代码撰写工具,有了这个工具至少提高了10%的工作效率。
后面看了一些hibernate-tools的源码…发现…它用的也是freemarks,但是讲述代码生成的文章还比较少,所以做个笔记。