在JPA项目中,通常实体与数据库之间的映射需要人工去写,容易写错,也比较繁琐,实际上JPA的实体映射方式也就那么几种,与其对着数据库表结构一句一句地去手写,不如用代码生成器去生成一下,本例中不涉及N-N的关系映射(即外键关系),下面贴出代码,希望各位Reader可以完善和点评一下。
项目是基于Maven构建,后期改成普通Java项目。下面是引用到的Jar文件:
工程目录结构:
下面贴出所有代码:
[Consts.java]
package com.able.consts;
/**
* 数据库常量
* @author zhanglikun
* @mail likun_zhang@yeah.net
* @date 2013-6-6
*/
public interface Consts {
String DB_NAME = "database_name" ; // 数据库名称
String DB_HOST = "database_server" ; // 数据库HOST
int DB_PORT = 3306 ; // 数据库端口
String DB_USER="root" ; // 用户名
String DB_PASS="database_password" ; // 密码
String DB_TABLE_PREFIX = "TBL_" ; // 表前缀
String TARGET_DIR = "G:/Temps/codes/" ; // 生成代码存放目录
}
[DBHandler.java]
package com.able.db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import com.able.consts.Consts;
/**
* 创建数据库连接
* @author zhanglikun
* @mail likun_zhang@yeah.net
* @date 2013-6-6
*/
public class DBHandler {
private static Connection conn ;
public static final Connection createConnection() {
try {
Class.forName("com.mysql.jdbc.Driver") ;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
conn = DriverManager.getConnection("jdbc:mysql://" + Consts.DB_HOST + ":" + Consts.DB_PORT + "/" + Consts.DB_NAME, Consts.DB_USER ,Consts.DB_PASS) ;
} catch (SQLException e) {
e.printStackTrace();
}
return conn ;
}
public static final void close() {
if(conn != null) {
try {
conn.close() ;
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
[AnalysisDB.java]
package com.able.db;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.able.consts.Consts;
import com.able.model.ColumnMeta;
import com.able.model.TableMeta;
/**
* 解析表结构
* @author zhanglikun
* @mail likun_zhang@yeah.net
* @date 2013-6-6
*/
public class AnalysisDB {
/**
* 读取表列表
* @return
*/
public static final List<TableMeta> readDB() {
List<TableMeta> list = new ArrayList<TableMeta>();
String sql = "SELECT TABLE_NAME ,TABLE_COMMENT FROM information_schema.tables "
+ " WHERE table_schema ='"+Consts.DB_NAME+"'";
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = DBHandler.createConnection().prepareStatement(sql);
rs = ps.executeQuery();
while (rs.next()) {
TableMeta tm = new TableMeta() ;
tm.setSchemaName(Consts.DB_NAME) ;
tm.setTableName(rs.getString("TABLE_NAME")) ;
tm.setComment(rs.getString("TABLE_COMMENT")) ;
list.add(tm) ;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
rs.close();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return list;
}
/**
* 填充字段信息
* @param tables
*/
public static final void readTables(List<TableMeta> tables) {
if(tables != null) {
for(TableMeta tm : tables) {
readTable(tm) ;
}
}
DBHandler.close() ;
}
/**
* 读取表结构
* @param table
*/
private static final void readTable(TableMeta table) {
List<ColumnMeta> list = new ArrayList<ColumnMeta>() ;
String sql = "SELECT COLUMN_NAME ,ORDINAL_POSITION ,IS_NULLABLE ,COLUMN_DEFAULT ,COLUMN_TYPE ,COLUMN_KEY ,EXTRA ,COLUMN_COMMENT "
+ " FROM information_schema.columns WHERE table_schema ='" + Consts.DB_NAME + "' AND table_name = '" + table.getTableName() + "'";
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = DBHandler.createConnection().prepareStatement(sql);
rs = ps.executeQuery();
while (rs.next()) {
ColumnMeta fm = new ColumnMeta() ;
fm.setColumnName(rs.getString("COLUMN_NAME")) ;
fm.setOrdinalPosition(rs.getString("ORDINAL_POSITION")) ;
fm.setIsNullable(rs.getString("IS_NULLABLE")) ;
fm.setColumnDefault(rs.getString("COLUMN_DEFAULT")) ;
fm.setColumnType(rs.getString("COLUMN_TYPE")) ;
fm.setColumnKey(rs.getString("COLUMN_KEY")) ;
fm.setExtra(rs.getString("EXTRA")) ;
fm.setColumnComment(rs.getString("COLUMN_COMMENT")) ;
list.add(fm) ;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
rs.close();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
table.setColumns(list) ;
}
}
[ColumnMeta.java]
package com.able.model;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
/**
* 字段结构
* @author zhanglikun
* @mail likun_zhang@yeah.net
* @date 2013-6-6
*/
public class ColumnMeta {
private String columnName ;
private String ordinalPosition ;
private String isNullable ;
private String columnDefault ;
private String columnType ;
private String columnKey ;
private String extra ;
private String columnComment ;
private int columnSize ; // 字段长度,将在计算属性类型时赋值
public String getColumnName() {
return columnName;
}
public void setColumnName(String columnName) {
this.columnName = columnName;
}
public String getOrdinalPosition() {
return ordinalPosition;
}
public void setOrdinalPosition(String ordinalPosition) {
this.ordinalPosition = ordinalPosition;
}
public String getIsNullable() {
return isNullable;
}
public void setIsNullable(String isNullable) {
this.isNullable = isNullable;
}
public String getColumnDefault() {
return columnDefault;
}
public void setColumnDefault(String columnDefault) {
this.columnDefault = columnDefault;
}
public String getColumnType() {
return columnType;
}
public void setColumnType(String columnType) {
this.columnType = columnType;
}
public String getColumnKey() {
return columnKey;
}
public void setColumnKey(String columnKey) {
this.columnKey = columnKey;
}
public String getExtra() {
return extra;
}
public void setExtra(String extra) {
this.extra = extra;
}
public String getColumnComment() {
return columnComment;
}
public void setColumnComment(String columnComment) {
this.columnComment = columnComment;
}
/**
* 获取属性名称
* @return
*/
public String getPropertyName() {
StringBuffer propertyName = new StringBuffer() ;
String [] names = StringUtils.split(columnName.toLowerCase(), "_") ;
propertyName.append(names[0]) ; // 第一个单词全小写
for(int i = 1 ,len = names.length ; i < len ; i ++) {
propertyName.append(names[i].substring(0, 1).toUpperCase() + names[i].substring(1)) ;
}
return propertyName.toString() ;
}
/**
* 获取属性类型
* @return
*/
public String getPropertyType() {
String type = columnType.toLowerCase() ;
String propertyType = null ;
if(StringUtils.startsWith(type, "int")) { // int/long
columnSize = NumberUtils.toInt(StringUtils.substring(type, StringUtils.indexOf(type, "(") + 1 ,StringUtils.indexOf(type, ")"))) ;
if(columnSize <= 4)propertyType = "Integer" ;
else propertyType = "Long" ;
} else if(StringUtils.startsWith(type, "bigint")) { // long
propertyType = "Long" ;
} else if(StringUtils.startsWith(type, "double")) { // double
propertyType = "Double" ;
} else if(StringUtils.startsWith(type, "float")) { // float
propertyType = "Float" ;
} else if(StringUtils.startsWith(type, "varchar")) { // String
columnSize = NumberUtils.toInt(StringUtils.substring(type, StringUtils.indexOf(type, "(") + 1 ,StringUtils.indexOf(type, ")"))) ;
propertyType = "String" ;
} else if(StringUtils.startsWith(type, "char")) { // String
columnSize = NumberUtils.toInt(StringUtils.substring(type, StringUtils.indexOf(type, "(") + 1 ,StringUtils.indexOf(type, ")"))) ;
propertyType = "String" ;
} else if(StringUtils.startsWith(type, "text")) { // String
propertyType = "String" ;
} else if(StringUtils.startsWith(type, "date")) { // date
propertyType = "java.util.Date" ;
} else if(StringUtils.startsWith(type, "datetime")) { // date
propertyType = "java.util.Date" ;
} else if(StringUtils.startsWith(type, "timestamp")) { // date
propertyType = "java.util.Date" ;
} else {
System.out.println("==类型[" + type + "]解析尚不支持==");
}
return propertyType ;
}
public int getColumnSize() {
return columnSize;
}
}
[TableMeta.java]
package com.able.model;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.able.consts.Consts;
/**
* 表结构信息
* @author zhanglikun
* @mail likun_zhang@yeah.net
* @date 2013-6-6
*/
public class TableMeta {
private String schemaName ;
private String tableName ;
private String comment ;
private List<ColumnMeta> columns ;
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public List<ColumnMeta> getColumns() {
return columns;
}
public void setColumns(List<ColumnMeta> columns) {
this.columns = columns;
}
public String getClassName() {
if(tableName == null) return "" ;
StringBuffer className = new StringBuffer() ;
if(StringUtils.startsWith(tableName, Consts.DB_TABLE_PREFIX)) {
String newName = StringUtils.substring(tableName, Consts.DB_TABLE_PREFIX.length()) ;
String [] names = StringUtils.split(newName.toLowerCase(), "_") ;
for(int i = 0 ,len = names.length ; i < len ; i ++) {
className.append(names[i].substring(0, 1).toUpperCase() + names[i].substring(1)) ;
}
} else {
System.out.println("==不支持的表前缀==");
}
return className.toString() ;
}
public String getSchemaName() {
return schemaName;
}
public void setSchemaName(String schemaName) {
this.schemaName = schemaName;
}
}
[GenerateMain.java]
package com.able;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.able.consts.Consts;
import com.able.db.AnalysisDB;
import com.able.model.TableMeta;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
/**
* 生成代码入口
* @author zhanglikun
* @date 2013-7-17
* @desc XXX
*/
public class GenerateMain {
public static void main(String[] args) throws IOException, TemplateException {
List<TableMeta> tableList ;
Writer out = null ;
String targetDir = Consts.TARGET_DIR ;
tableList = AnalysisDB.readDB() ;
AnalysisDB.readTables(tableList) ;
// 输出到文件
File dir = new File(targetDir) ;
if(!dir.isDirectory()) {
dir.mkdirs() ;
}
Configuration cfg = new Configuration() ;
cfg.setDirectoryForTemplateLoading(new File("src/main/tpl")) ;
cfg.setObjectWrapper(new DefaultObjectWrapper());
Template tpl = cfg.getTemplate("model.ftl") ;
if(tableList != null) {
for(TableMeta tm : tableList) {
if(StringUtils.isBlank(tm.getClassName()))continue ;
out = new FileWriter(new File(targetDir + tm.getClassName() + ".java")) ;
tpl.process(tm, out) ;
System.out.println("===文件 " + tm.getClassName() + ".java" + " 生成成功===");
}
}
out.flush() ;
out.close() ;
}
}
[model.ftl]
<#-- TODO 改为实际使用的包名 -->
package com.able.model ;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import com.able.consts.UCCConsts;
/**
* ${comment!""}
* @desc 使用代码生成器生成.
* @date ${.now?string("yyyy/MM/dd")}
*/
@Entity
@Table(name = "${tableName}" ,catalog = UCCConsts.APP_SCHEMA)
public class ${className} extends IDEntity {
<#list columns as column>
<#assign autograph = "private " + column.propertyType + " " + column.propertyName + " ;">
<#if column.propertyName != "id">
${autograph}<#if (column.columnComment?? && column.columnComment?length > 0)><#if (autograph?length < 4)>${"\t\t\t\t\t\t\t\t\t\t"}<#elseif (autograph?length < 8)>${"\t\t\t\t\t\t\t\t\t"}<#elseif (autograph?length < 12)>${"\t\t\t\t\t\t\t\t"}<#elseif (autograph?length < 16)>${"\t\t\t\t\t\t\t"}<#elseif (autograph?length < 20)>${"\t\t\t\t\t\t"}<#elseif (autograph?length < 24)>${"\t\t\t\t\t"}<#elseif (autograph?length < 28)>${"\t\t\t\t"}<#elseif (autograph?length < 32)>${"\t\t\t"}<#elseif (autograph?length < 36)>${"\t\t"}<#else>${"\t"}</#if>// ${column.columnComment}</#if>
</#if>
</#list>
<#list columns as column>
<#if column.propertyName != "id">
public void set${column.propertyName?cap_first} (${column.propertyType} ${column.propertyName}){
this.${column.propertyName} = ${column.propertyName} ;
}
<#if column.propertyType == "java.util.Date">@javax.persistence.Temporal(javax.persistence.TemporalType<#if column.columnType == "DATE" || column.columnType == "date">.DATE<#else>.TIMESTAMP</#if>)</#if>
@Column(name = "${column.columnName}"<#if column.columnSize != 0> ,length = ${column.columnSize}</#if><#if column.isNullable == "NO"> ,nullable = false</#if><#if column.columnKey == "UNI"> ,unique = true</#if>)
public ${column.propertyType} get${column.propertyName?cap_first} (){
return this.${column.propertyName} ;
}
</#if>
</#list>
}
运行:GenerateMain即可生成代码,生成代码配置在Consts中完成。
注:本项目仅适用于MySQL数据库。
其中的IDEntity可自行编写,下面是我的,当然未必需要对ID做统一映射:
package com.able.model;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
/**
* 公共主键映射
* @author ZhangLiKun
* @title IDEntity
* @date 2013-6-6
*/
@MappedSuperclass
public class IDEntity {
private Long id ;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
下面贴出的是一个生成后的Model代码:
package com.able.model ;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
* 推课表
* @desc 使用代码生成器生成.
* @date 2013/07/17
*/
@Entity
@Table(name = "TBL_RECOMMEND" ,catalog = "COURSE_CENTER")
public class Recommend extends IDEntity {
private Long parentId ; // 父ID
private Long recommendCourseId ; // 推课ID
private Long userId ; // 用户ID
private String reason ; // 推课理由
private java.util.Date createdTime ; // 推课时间
public void setParentId (Long parentId){
this.parentId = parentId ;
}
@Column(name = "PARENT_ID" ,length = 11 ,nullable = false)
public Long getParentId (){
return this.parentId ;
}
public void setRecommendCourseId (Long recommendCourseId){
this.recommendCourseId = recommendCourseId ;
}
@Column(name = "RECOMMEND_COURSE_ID" ,length = 11 ,nullable = false)
public Long getRecommendCourseId (){
return this.recommendCourseId ;
}
public void setUserId (Long userId){
this.userId = userId ;
}
@Column(name = "USER_ID" ,length = 11 ,nullable = false)
public Long getUserId (){
return this.userId ;
}
public void setReason (String reason){
this.reason = reason ;
}
@Column(name = "REASON" ,length = 500)
public String getReason (){
return this.reason ;
}
public void setCreatedTime (java.util.Date createdTime){
this.createdTime = createdTime ;
}
@javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP)
@Column(name = "CREATED_TIME" ,nullable = false)
public java.util.Date getCreatedTime (){
return this.createdTime ;
}
}