ssm 自定义注解实现mybatis自动维护表结构以及利用freemarker生成代码

时间:2022-05-16 12:27:42
在开发过程中,我们经常会遇到以下两个问题:
1、表结构的维护。使用hibernate不方便进行特殊业务语句的操作,而实际开发过程中经常需要一些特殊的操作而不能像hibernate提倡的那样对对象进行操作;使用mybatis自动创表又不能满足要求。
2、代码MVC结构的创建过程,新开发一个功能,经常要创建各种文件,如interface,impl,service等。
这里分享一个 ssm框架 利用注解pojo类进行数据库表结构的维护以及利用freemarker对模板进行自动化生成java 代码。这里的模板目前只开发到自动生成treepanel 的展示,增加删除修改还未加入模板。后续会逐步进行完善升级。
首先我们来看看如何利用注解pojo类来自动维护数据库表结构。首先我们定义一个自己的注解interface接口。com.auto.anotation.Table.java
package com.auto.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 自定义表注解
 * @author chenfuqiang
 *
 */
//表示注解加在接口、类、枚举等
@Target(ElementType.TYPE)
//VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息
@Retention(RetentionPolicy.RUNTIME)
//将此注解包含在javadoc中
@Documented
//允许子类继承父类中的注解
@Inherited
public @interface Table {
	/**
	 * 默认值当前类名
	 * 也可以指定个性化表名,提供参数如下:
	 * @year : 传递当前年份
	 * @month: 传递当前月份
	 * @return 表名
	 */
	public abstract String name() default "";
	/**
	 * 默认 false
	 * 是否包含父类只支持一次继承
	 * @return
	 */
	public abstract boolean includeSupperClass() default false;
	
	/**
	 * 默认值 “”
	 * 设定js panel title
	 * @return
	 */
	public abstract String jsname() default "";
}
这里我们定义了 name 数据库表的名称、includeSupperClass 是否包含父节点的字段 以及待会儿自动生成代码需要用到的jsname 表示panel需要显示的中文名称。

我们还需要定义每个列的信息,所以我们接着定义一个自定义的注解接口 com.auto.annotation.Column.java
package com.auto.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义列注解
 * @author chenfuqiang
 *
 */
//该注解用于方法声明
@Target(ElementType.FIELD)
//VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息
@Retention(RetentionPolicy.RUNTIME)
//将此注解包含在javadoc中
@Documented
//允许子类继承父类中的注解
@Inherited
public @interface Column {
	/**
	 * 默认当前字段名。
	 * @return
	 */
	public abstract String name() default "";
	/**
	 * 默认值是normal
	 * 仅可以填写【primary,normal】其他无效。
	 * 可以多个字段都是primary做联合主键。
	 * @return
	 */
	public abstract String flag() default "normal";
	/**
	 * 默认值是varchar(50)
	 * 【varchar(50),decimal(18,2),int,smallint,...】
	 * 字段类型,支持SQLSERVER数据库类型
	 * @return
	 */
	public abstract String type() default "varchar(50)";
	
	/**
	 * 字段默认值
	 * 支持 newid()方法
	 * 支持变量如下【@hbdwbh,@dwbh,@year,@month】
	 * @return
	 */
	public abstract String defaultValue() default "";
	/**
	 * 【identity(1,1),NOT NULL,NULL】
	 * 自增,非空,空,默认空值
	 * 默认不写表示primary则是NOT NULL normal是NULL 如果是normal 想要NOT NULL 可以加OTH="NOT NULL"
	 * @return
	 */
	public abstract String oth() default "";
	
	
	//=====================================JS 设定部分===========================================//
	/**
	 * 生成js model时候的数据类型,如果不指定,他会根据type来判断类型
	 * @return
	 */
	public abstract String jstype() default "";
	
	/**
	 * 生成grid 或者tree 的列名称 如果为空则不显示在页面上
	 * @return
	 */
	public abstract String jsname() default "";
	
	/**
	 * 如果是树的话需要指定主列
	 * @return
	 */
	public abstract boolean treecolumn() default false;
	
}

那么我们的pojo类就可以指定数据库的名称,以及通过column来指定每个字段对应数据库的类型,范例如下:
package com.main.pojo;

import java.io.Serializable;

import com.auto.annotation.AutoCode;
import com.auto.annotation.Column;
import com.auto.annotation.Table;

/**
 * 绩效树字典
 * @author chenfuqiang
 *
 */
//下面可以指定数据库的表名,后面的年份后期可以通过解析进行自动替换为业务登录的时间或者系统当前的时间
@Table(name="asm_performance@year",includeSupperClass=false,jsname = "指标对象字典")
//这里表示这个类是否需要自动根据模板生成代码,可以自动生成controller,service,serviceImpl,dao,mapping.xml,以及前端的js,jsp
@AutoCode(isOverride=true,includeSupperClass=false)

public class Performance extends TreeNodeBean implements Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = -3779425929815980773L;
	@Column(name="code",type="int",flag="primary",oth="identity(1,1)")
	private int code;
	@Column(type = "varchar(10)")
	private String hbdwbh;
	@Column(type="varchar(10)")
	private String dwbh;
	@Column(name="name",type="varchar(500)",flag="normal",oth="NOT NULL",treecolumn=true,jsname="指标对象")
	private String name;
	@Column(type="int")
	private int parentId;
	@Column(type="int")
	private int isleaf;
	@Column(type="int")
	private int grade;
	@Column(type="int")
	private int orderId;
	@Column(type="int")
	private int scoreFlag;//-1加分 1扣分
	@Column(type="varchar(5)")
	private String objOrZb;
	@Column(type="decimal(18,2)",jsname="分值")
	private String topScore;
	@Column(type="decimal(18,2)")
	private String perc;
	@Column(type="varchar(1000)",jsname="指标说明")
	private String codeDesc;
	@Column(type="varchar(1000)",jsname="指标要求")
	private String codeRequ;
	@Column(type="varchar(1000)",jsname="考核办法")
	private String codeWay;
	//下面的getters and setters 忽略。详细的可以下载范例代码进行查看。	
	
}

那么我们如何具体根据pojo类的这些注解信息来自动维护数据库的表结构呢?这里编写了一个接口和一个实现类,整体是通过反射的机制来实现的。
接口类:com.auto.service.AutoFixTable.java 实现类:com.auto.service.impl.AutoFixTableSQLServerImpl.java
package com.auto.service;

import com.main.pojo.DealInfo;
import com.main.pojo.YeWu;

/**
 * 自动扫描pojo,进行注解解析生成表结构语句并维护表结构
 * @author chenfuqiang
 *
 */
public interface AutoFixTable {
	public abstract DealInfo run(YeWu yw);
}
package com.auto.service.impl;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

import com.auto.annotation.Column;
import com.auto.annotation.Table;
import com.auto.service.AutoFixTable;
import com.common.ArrayUtils;
import com.common.ClassTools;
import com.common.CommonUtil;
import com.common.Global;
import com.common.Log4JUtil;
import com.main.dao.CommonDao;
import com.main.pojo.DealInfo;
import com.main.pojo.YeWu;

/**
 * 自动扫描ebg.pojo包下的实体类,进行注解解析生成表结构语句并维护表结构
 * @author chenfuqiang
 *
 */
@Service("autoFixTable")
public class AutoFixTableSQLServerImpl implements AutoFixTable{
	/**
	 * 要扫描的model所在的pack
	 */
	private String pack = Global.PACKAGEOFPOJO;
	@Resource
	private CommonDao commonDao;
	
	@Override
	public DealInfo run(YeWu yw) {
		long begin = System.currentTimeMillis();
		Log4JUtil.info("---开始自动修复表结构---");
		Set<Class<?>> classes = ClassTools.getClasses(pack);
		DealInfo dealInfo = dealClassSQL(classes,yw);
		long end = System.currentTimeMillis();
		Log4JUtil.info("---结束自动修复表结构---");
		Log4JUtil.info("总共花费了"+((end-begin)*0.001)+"秒");
		return dealInfo;
	}
	
	public DealInfo dealClassSQL(Set<Class<?>> classes,YeWu yw) {
		DealInfo dealInfo = new DealInfo();
		List<String> sqlAdd = new ArrayList<String>();
		List<String> sqlUpt = new ArrayList<String>();
		List<String> sqlPK = new ArrayList<String>();
 		List<String> sqlEtc = new ArrayList<String>();
		Map<String,String> table_indexName = this.getKeyName(Global.DTDBNAME);
		StringBuffer keyBuf = new StringBuffer();
		StringBuffer keyEditBuf = new StringBuffer();
		StringBuffer allBuf = new StringBuffer();
		StringBuffer addBuf = new StringBuffer();
		StringBuffer editBuf = new StringBuffer();
		StringBuffer pkBuf = new StringBuffer();
		StringBuffer dvBuf = new StringBuffer();
		String year = null;
		if(yw != null ) {
			year = yw.getTheyear();
		}else {
			year = new SimpleDateFormat("yyyy").format(new Date());
		}
		
		for (Class<?> clas : classes){
			addBuf.setLength(0);
			keyBuf.setLength(0);
			keyEditBuf.setLength(0);
			pkBuf.setLength(0);
			editBuf.setLength(0);
			dvBuf.setLength(0);
			allBuf.setLength(0);
			
			Table table = clas.getAnnotation(Table.class);
			if(table == null) {
				continue;
			}
			String tablename = table.name();
			if(CommonUtil.isNullStr(tablename)) {
				tablename = clas.getSimpleName();
			}
			tablename = tablename.toUpperCase().replace("@YEAR", year); 
			//==================================================================================//
			//去掉索引,然后再修改列
			String indexName = table_indexName.get(tablename.toUpperCase());
			if(!CommonUtil.isNullStr(indexName)){
				pkBuf.append("ALTER TABLE "+tablename+" drop CONSTRAINT "+indexName+" \r\n");
				sqlPK.add(pkBuf.toString());
			}
			addBuf.append("CREATE TABLE [dbo].[").append(tablename).append("]( \r\n");
			
			Field[] fields = clas.getDeclaredFields();

// 			这里支持集成的父类,要支持只要把下面的fields 附加到子类的fields即可。
			if(table.includeSupperClass()) {
				if(clas.getSuperclass()!=null){
					Class<?> clsSup = clas.getSuperclass();
					fields = (Field[]) ArrayUtils.addAll(fields,clsSup.getDeclaredFields());
				}
			}
			for (Field field : fields){
				Column column = field.getAnnotation(Column.class);
				if(column == null) {continue;}
				String columnname = CommonUtil.isNullStr(column.name())?field.getName():column.name();
				String flag = column.flag();
				String dv = column.defaultValue();
				String oth = column.oth();//identity(1,1)
				if(!CommonUtil.isNullStr(dv)&&yw!=null) {
					dv = dv.toUpperCase().replace("@HBDWBH", yw.getHbdwbh());
					dv = dv.toUpperCase().replace("@DWBH", yw.getDwbh());
					dv = dv.toUpperCase().replace("@YEAR", year);
					dv = dv.toUpperCase().replace("@MONTH", yw.getThemonth());
				}
				String type = column.type();
				
				addBuf.append("[").append(columnname).append("] ").append(type).append(" ");
				if(!CommonUtil.isNullStr(oth)) {
					addBuf.append(" "+oth+" ");
				}
				if("PRIMARY".equals(CommonUtil.nullToStr(flag).toUpperCase())) {
					keyBuf.append("[").append(columnname).append("] ASC,\r\n");
					keyEditBuf.append(columnname).append(",");
					
					addBuf.append(" NOT NULL ");
				}
				if(!CommonUtil.isNullStr(dv)&&yw!=null) {
					addBuf.append(" DEFAULT (").append(dv).append(") ");
					dvBuf.append("Update ").append(tablename).append(" Set ").append(columnname).append("=").append(dv).append(" ");
					dvBuf.append("Where ").append(columnname).append(" is null").append(" \r\n");
				}
				addBuf.append(",\r\n");
				//===================================UPDATE FIELDS=================================//
				editBuf.append("IF EXISTS(SELECT * FROM syscolumns WHERE ID=OBJECT_ID('"+tablename+"') AND NAME='"+columnname+"') \r\n");
				editBuf.append("BEGIN \r\n");
				editBuf.append("ALTER TABLE ").append(tablename).append(" ALTER column ").append(columnname).append(" ").append(type).append(" ");
				if("PRIMARY".equals(CommonUtil.nullToStr(flag).toUpperCase())) {
					editBuf.append(" NOT NULL ");
				}else {
					if(!CommonUtil.isNullStr(oth)) {
						editBuf.append(" NOT NULL ");
					}
				}
				editBuf.append(" \r\n");
				editBuf.append("END \r\n");
				editBuf.append("IF NOT EXISTS(SELECT * FROM syscolumns WHERE ID=OBJECT_ID('"+tablename+"') AND NAME='"+columnname+"') \r\n");
				editBuf.append("BEGIN \r\n");
				editBuf.append("ALTER TABLE ").append(tablename).append(" add ").append(columnname).append(" ").append(type).append(" ");
				if("PRIMARY".equals(CommonUtil.nullToStr(flag).toUpperCase())) {
					editBuf.append(" NOT NULL ");
				}else {
					if(!CommonUtil.isNullStr(oth)) {
						editBuf.append(" NOT NULL ");
					}
				}
				editBuf.append(" \r\n");
				editBuf.append("END \r\n");
				//=================================================================================//
			}
			if(keyBuf.length() != 0) {
				addBuf.append("CONSTRAINT [PK_" + tablename+ "] PRIMARY KEY CLUSTERED ( \r\n");
				addBuf.append(keyBuf.substring(0,keyBuf.length()-3));
				addBuf.append(") ON [PRIMARY] \r\n");
			}else {
				addBuf.delete(addBuf.length()-3, addBuf.length()-1);
			}
			//__________________________TEST_____________________________//
//				if(tablename.equals("TYS_XMYS_FIELDS")) {
//					System.out.println(editBuf.toString());
//				}
			
			//___________________________END_____________________________//
			addBuf.append(") ON [PRIMARY] \r\n");
			
			allBuf.append("IF EXISTS (SELECT * FROM sysobjects WHERE id = OBJECT_ID(N'[dbo].["+ tablename+ "]') AND OBJECTPROPERTY(id, N'IsUserTable') = 1) \r\n");
			allBuf.append("BEGIN \r\n");
			allBuf.append(editBuf.toString());
			allBuf.append("END \r\n");
			sqlUpt.add(allBuf.toString());
			
			allBuf.append("IF NOT EXISTS (SELECT * FROM sysobjects WHERE id = OBJECT_ID(N'[dbo].["+ tablename+ "]') AND OBJECTPROPERTY(id, N'IsUserTable') = 1) \r\n");
			allBuf.append("BEGIN \r\n");
			allBuf.append(addBuf.toString());
			allBuf.append("END \r\n");
			sqlAdd.add(allBuf.toString());
			
			//修改主键需要在列都修改完执行完之后再修改主键,因为有些列是NULL,修改完列后就是NOT NULL
			//=====================================UPDATE TABLE ===============================//
			//修改默认值
			if(dvBuf.length() != 0) {
				sqlEtc.add(dvBuf.toString());
			}
			//修改主键
			if(keyBuf.length() != 0) {
				allBuf.setLength(0);
				allBuf.append("IF EXISTS (SELECT * FROM sysobjects WHERE id = OBJECT_ID(N'[dbo].["+ tablename+ "]') AND OBJECTPROPERTY(id, N'IsUserTable') = 1) \r\n");
				allBuf.append("BEGIN \r\n");
				allBuf.append("alter table "+tablename+" add constraint pk_"+tablename+" primary key ("+keyEditBuf.substring(0,keyEditBuf.length()-1)+") \r\n");
				allBuf.append("END \r\n");
				sqlEtc.add(allBuf.toString());
			}
		}
		//======================================JDBC===============================================//
		try {
			
			if(sqlAdd.size()>0) {
				commonDao.transactionUpdate(sqlAdd);
				Log4JUtil.info("--ADD TABLE END--");
			}
			if(sqlUpt.size()>0) {
				commonDao.transactionUpdate(sqlUpt);
				Log4JUtil.info("--UPT TABLE END--");
			}
			if(sqlPK.size()>0) {
				commonDao.transactionUpdate(sqlPK);
				Log4JUtil.info("--PK DROP END--");
			}
			if(sqlEtc.size()>0) {
				commonDao.transactionUpdate(sqlEtc);
				Log4JUtil.info("--ETC OPT END--");
			}
			
		} catch (Exception e) {
			e.printStackTrace();
			Log4JUtil.error(e.getMessage());
			dealInfo.setFlag(false);
			dealInfo.setInfo(e.getMessage());
		}
		return dealInfo;
	}

	
	public Map<String,String> getKeyName(String db) {
//		select a.name,b.name from sysobjects a left join sysobjects b on a.parent_obj = b.id where 1=1 
//		and a.xtype='pk'
		StringBuffer sqlBuffer = new StringBuffer();
		sqlBuffer.append("select b.name AS [表名], ");
		sqlBuffer.append("a.name  AS [主键名称] ");
		sqlBuffer.append("from "+db+".dbo.sysobjects a left join "+db+".dbo.sysobjects b on a.parent_obj = b.id where 1=1 ");
		sqlBuffer.append("and a.xtype='pk' ");
		List<Map<String,Object>> list = commonDao.getListForMap(sqlBuffer.toString());
		Map<String,String> result = new HashMap<String, String>();
		for(Map<String,Object> m : list) {
			result.put(String.valueOf(m.get("表名")).toUpperCase(), String.valueOf(m.get("主键名称")));
		}
		return result;
	}
	
}
当你定义了一个自己的pojo类的时候,可以通过com.main.controller.MainController.java 进行访问测试。这里pojo包是通过全局变量进行配置的。如果你想使用这个功能可能需要自己进行代码的修改。

package com.main.controller;


import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.chainsaw.Main;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import com.auto.service.AutoCode;
import com.auto.service.AutoFixTable;
import com.main.dao.CommonDao;
import com.main.service.IUserService;
/**
 * 测试用例
 * @author chenfuqiang
 *
 */
@Controller
public class MainController {
	@Resource
	private IUserService userService;
	@Resource
	private AutoFixTable autoFixTable;
	@Resource
	private AutoCode autoCode;
	/**
	 * 样例
	 * @param request
	 * @param model
	 * @return
	 */
	@RequestMapping("/code")
	public String toIndex(HttpServletRequest request,Model model){
		//根据注解自动修复表结构
		autoFixTable.run(null);
		//根据注解自动生成代码
		autoCode.run(true);
		
		return "ShowUser";
	}
	
	@RequestMapping("/decode")
	public String toIndexX(HttpServletRequest request,Model model){
		//根据注解自动修复表结构
		autoFixTable.run(null);
		//根据注解自动生成代码
		autoCode.run(false);
		
		return "ShowUser";
	}
}

接下来说一下自动生成代码这块
与自动维护数据库表结构类似,我们也需要定义一个自己的注解接口来声明这个pojo是否需要自动生成代码。所有自动生成相关的代码都在auto包下面。
com.auto.annotation.AutoCode.java
package com.auto.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自动生成代码注解
 * @author chenfuqiang
 *
 */
//表示注解加在接口、类、枚举等
@Target(ElementType.TYPE)
//VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息
@Retention(RetentionPolicy.RUNTIME)
//将此注解包含在javadoc中
@Documented
//允许子类继承父类中的注解
@Inherited
public @interface AutoCode {
	/**
	 * 是否可以覆盖原先自动生成的文件
	 * 默认不覆盖已有的文件
	 * @return
	 */
	public abstract boolean isOverride() default false;
	/**
	 * 默认false
	 * 是否包含pojo对象的父类,这里只支持一层继承关系
	 * @return
	 */
	public abstract boolean includeSupperClass() default false;
}

这里介绍一下apache-freemarker 是一个根据模板可以自动替换里面参数生成文件的一个模板引擎,这里拿他来做自动生成代码。具体freemarker的资料可以查看下面的链接。

api :

点击打开链接

web :

 官网

中文文档


freemarker需要一个Configuration对象,这个对象官方建议不要重复创建,这里我们写一个自己的类实现单例模式 com.auto.service.impl.FMConfiguration.java

package com.auto.service.impl;

import java.io.File;
import java.io.IOException;

import com.common.Global;

import freemarker.template.Configuration;
import freemarker.template.TemplateExceptionHandler;

public class FMConfiguration {
	private static Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
	static {
		try {
//			cfg.setDirectoryForTemplateLoading(new File(System.getProperty("user.dir")+Global.TEMPLATEPATH));
			cfg.setDirectoryForTemplateLoading(new File(Global.WORKSPACEPROPATH+Global.TEMPLATEPATH));
			//cfg.setDefaultEncoding("UTF-8");
			cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
			cfg.setDefaultEncoding(Global.ENCODE);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	private FMConfiguration() {
		
	}
	public static Configuration getInstance() throws IOException {
		return cfg;
	}

}

由于后面可能拓展出很多的自动生成代码需要的具体实现,这里我们封装一个接口  Coder ,如果你要自己写一个controller的coder则可以实现这个接口,同时继承一个BaseCoder类,里面实现了一个通用的coder方法。所有的类路径和包名都是通过 Global.java进行全局定义。我们来看看Coder.java

package com.auto.service;

import java.util.Map;

import com.main.pojo.DealInfo;
import freemarker.template.Configuration;

/**
 * 编写器
 * 若要编写一个自己的编写器,需要继承此接口
 * 示例请看impl 包下的Coder
 * @author chenfuqiang
 *
 */
public interface Coder {
	
	/**
	 * 具体如何编写
	 * cfg : FMConfiguration 获取
	 * root: 拼凑需要替换的变量
	 * isOverride:如果发现了已经有自动生成的代码文件了是否替换
	 * @return
	 */
	public abstract DealInfo coder(Configuration cfg,Map<String,Object> root,boolean isOverride);
	/**
	 * 删除已经自动根据模板生成的文件
	 * @param cfg
	 * @param root
	 * @param isOverride
	 * @return
	 */
	public abstract DealInfo decoder(Configuration cfg,Map<String,Object> root,boolean isOverride);
	/**
	 * 根据pojo来获取参数root
	 * @param pojo
	 * @return
	 */
	public abstract Map<String,Object> getRoot(Class<?> pojo);
	/**
	 * 目标包名
	 * @return
	 */
	public abstract String getTargetPackage();
	/**
	 * 目标路径
	 * @return
	 */
	public abstract String getTargetPath();
	/**
	 * src路径
	 * @return
	 */
	public abstract String getSrcPath();
	/**
	 * 获取模板路径和名称
	 * @return
	 */
	public abstract String getFtlPath();
	/**
	 * 
	 * 获取文件结尾名称
	 * @return
	 */
	public abstract String getClassTail();
	
	
}

BaseCoder.java

package com.auto.service.impl;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Map;

import com.auto.service.Coder;
import com.common.Global;
import com.common.Log4JUtil;
import com.main.pojo.DealInfo;

import freemarker.template.Configuration;
import freemarker.template.Template;
/**
 * @author chenfuqiang
 * 抽象类
 * 这里实现的通用的coder方法和decoder方法;getTargetPath方法基本通用,继承此抽象类的子类
 * 无需再编写这三个方法,若有个性化操作,只要重写这三个方法即可。
 *
 */
public abstract class BaseCoder implements Coder{
	@Override
	public DealInfo coder(Configuration cfg, Map<String, Object> root,boolean isOverride) {
		DealInfo dealInfo = new DealInfo();
		try {
			String fileName = 
					Global.WORKSPACEPROPATH
					+this.getSrcPath()+this.getTargetPath()+"//"+root.get("className")+this.getClassTail();
			if(!isOverride) {
				File file = new File(fileName);
				if(file.exists()) {
					dealInfo.setFlag(true);
					dealInfo.setInfo("文件已存在,不进行覆盖生成!若要覆盖,请在pojo bean 注解@AutoCode(isOverride=true)");
					Log4JUtil.info("【忽略】生成文件:"+this.getTargetPath()+"//"+root.get("className")+this.getClassTail()+"【"+dealInfo.getInfo()+"】");
					return dealInfo;
				}
			}
			Template template = cfg.getTemplate(this.getFtlPath(),Global.ENCODE);
			Writer out = new OutputStreamWriter(new FileOutputStream(fileName),Global.ENCODE);
			template.process(root, out);
			out.flush();
			Log4JUtil.info("生成文件:"+this.getTargetPath()+"//"+root.get("className")+this.getClassTail());
		} catch (Exception e) {
			e.printStackTrace();
			dealInfo.setFlag(false);
			dealInfo.setInfo(e.getMessage());
		}
		return dealInfo;
	}
	
	@Override
	public DealInfo decoder(Configuration cfg,Map<String,Object> root,boolean isOverride) {
		DealInfo dealInfo = new DealInfo();
		try {
			if(isOverride) {
				String fileName = Global.WORKSPACEPROPATH+this.getSrcPath()+this.getTargetPath()+"//"+root.get("className")+this.getClassTail();
				File file = new File(fileName);
				if(file.exists()) {
					file.delete();
				}
				Log4JUtil.info("【删除】生成文件:"+this.getTargetPath()+"//"+root.get("className")+this.getClassTail());
			}
		}catch(Exception e) {
			e.printStackTrace();
			dealInfo.setFlag(false);
			dealInfo.setInfo(e.getMessage());
			Log4JUtil.error("FIELD DELETE FILE : "+this.getTargetPath()+"//"+root.get("className")+this.getClassTail()+e.getMessage());
		}
		return dealInfo;
	}
	
	public String getTargetPath() {
		String p = this.getTargetPackage();
		String[] ps = p.split("\\.");
		if(ps == null || ps.length == 0) {
			ps = new String[1];
			ps[0] = p;
		}
		StringBuffer targetPath = new StringBuffer();
		for(String t : ps) {
			targetPath.append("//").append(t);
		}
		return targetPath.toString();
	}
}

所有模板放置于com.auto.service.ftl 所有的coder实现类放置于 com.auto.service.impl.coder

这里还写了一个公用的入口接口和实现类,目的是自动扫描pojo包,并进行判断是否需要自动生成代码。

package com.auto.service;

import com.main.pojo.DealInfo;

/**
 * 自动生成模板代码
 * @author chenfuqiang
 *
 */
public interface AutoCode {
	/**
	 * 开始运行自动生成代码
	 * @param codeFlag :
	 * true 根据模板自动生成代码文件
	 * false 删除自动生成文件
	 * 只会删除 @AutoCode(isOverride=true) pojo 对应的文件 
	 * @return
	 */
	public abstract DealInfo run(boolean codeFlag);
}

package com.auto.service.impl;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import org.springframework.stereotype.Service;
import com.auto.annotation.AutoCode;
import com.auto.service.Coder;
import com.common.ClassTools;
import com.common.Global;
import com.common.Log4JUtil;
import com.main.pojo.DealInfo;

import freemarker.template.Configuration;

@Service("autoCode")
public class AutoCodeByFreeMarker implements com.auto.service.AutoCode{
	
	@Override
	public DealInfo run(boolean codeFlag) {
		long begin = System.currentTimeMillis();
		Log4JUtil.info("---开始代码生成---");
		//简单的pojo对象
		Set<Class<?>> classes = ClassTools.getClasses(Global.PACKAGEOFPOJO);
		//编写器
		Set<Class<?>> classesCoder = ClassTools.getClasses(Global.PACKAGEOFCODER);
		DealInfo dealInfo = new DealInfo();
		try {
			dealInfo = autoCode(classes,classesCoder,codeFlag);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			dealInfo.setFlag(false);
			dealInfo.setInfo(e.getMessage());
		}
		long end = System.currentTimeMillis();
		Log4JUtil.info("---结束代码生成---");
		Log4JUtil.info("总共花费了"+((end-begin)*0.001)+"秒");
		return dealInfo;
	}

	private DealInfo autoCode(Set<Class<?>> classes,Set<Class<?>> classesCoder,boolean code) throws Exception{
		Configuration cfg = FMConfiguration.getInstance();
		DealInfo dealInfo = new DealInfo();
		String funcName = "coder";
		if(!code) {
			funcName = "decoder";
		}
		for (Class<?> clas : classes){
			AutoCode auto = clas.getAnnotation(AutoCode.class);
			if(auto == null) {
				continue;
			}
			boolean isOverride = auto.isOverride();
			/**
			 * 循环执行各个编写器。
			 */
			for(Class<?> clasCoder : classesCoder) {
				Method doMethod = null; 
				doMethod = clasCoder.getMethod(funcName,Configuration.class,Map.class,boolean.class);
				Coder coder =(Coder)clasCoder.newInstance();
				Map<String,Object> root = coder.getRoot(clas);
				DealInfo dealOneInfo = (DealInfo) doMethod.invoke(coder,cfg, root, isOverride);
				dealInfo.setFlag(dealInfo.getFlag()&&dealOneInfo.getFlag());
				if(!dealOneInfo.getFlag()) {
					dealInfo.setInfo(dealInfo.getInfo()+";"+dealOneInfo.getInfo());
				}
			}
		}
		return dealInfo;
	}

	
}

具体测试,需要访问项目 http://localhost:8080/asm/code进行测试,也就是maincontroller  decode 即对已经生成的文件进行删除。


项目需要运行需要配置文件,这里使用是本公司业务要求的数据库链接模式,大家可以自行修改代码获取数据库的链接模式。如下图是本代码获取数据库的链接文件所在路径。

1、需要在tomcat 下建立 ecan_config文件夹,然后把 Ecan_YS_SysConfig.xml放入,配置三个数据库都为同一个数据库进行测试。

2、这里需要使用maven 进行包的导入,jdk使用1.8。具体可以下载代码进行查询。


程序使用的是通过自定义配置的xml读取数据库链接,获取链接是通过自己重写一个datasource类。然后配置到spring配置文件中去,各位可以自行修改为直接配置到spring文件中。具体代码在spring-mybatis.xml

<!-- 自己重写的一个数据源获取平台配置信息生成数据源 -->
	<bean id="dataSource" class="com.common.ComboPooledDataSource2DT"></bean>
	<bean id="dataSource4PF" class="com.common.ComboPooledDataSource2PF"></bean>

大家也可以通过这种方法来动态确定数据源。


项目代码下载  点击打开链接


ssm 自定义注解实现mybatis自动维护表结构以及利用freemarker生成代码