让Hibernate生成的DDL脚本自动增加注释
我们知道可以通过Hibernate对象自动生成DDL建表语句,通过PowerDesigner工具可以反向工程生成数据字典,但是在生成的DDL中一直不能写上中文的注释,这就使我们生成的数据字典不具有可用性。
这个假期宅在家里调试代码,发现Hibernate的Dialect,Table,Column的映射中已经做了comment的处理,只是Hibernate团队认为这个功能的重要性太小,一直没有时间提供这个需求,于是就自己动手实现这个功能了,这可是使用我们的数据对象代码与数据字典文档同步的关键一环啊!
通过对Hibernate代码的跟踪发现了处理映射的逻辑是在代码AnnotationBinder中,我们不需要在运行期间处理comment,只是在用SchemaExport时处理就可以,于是简单的实现了此功能:
1. 增加一个注解 @Comment("这是表的说明,也可以是字段的说明"),适用在类名和属性名
2. 复制org.hibernate.cfg.AnnotationBuilder代码为org.hibernate.cfg.MyAnnotationBuilder,处理注解@Comment
3. 复制org.hibernate.cfg.Configuration为org.hibernate.cfg.MyConfiguration,将AnnotationBuilder的调用换成MyAnnotationBuilder
3. 实现SchemaExportTool类,传入MyConfiguration处理Hibernate对象的映射关系,并生成DDL
以上思路在基于Hibernate 4.2.3版本在MySql 5.1上测试成功,因为代码只是在开发期运行,不需要良好的结构和优化,所以只是简单实现了,需要此功能的朋友可以自己实现。
以下是处理结果示例:
@Entity @Table(name = "AuditRecord_") @Comment("系统审计表") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) public class AuditRecord implements Serializable { private static final long serialVersionUID = 8844543936912679937L; @Id @GeneratedValue @Comment("ID主键") private Long id; @Comment("审计类型") @Column(length = 30) private String auditType; @Comment("操作人") @ManyToOne @JoinColumn(name = "userId") private Passport operator; // 操作简述 @Comment("操作简述") @Column(length = 4000) private String description; @Comment("创建时间") private Date creationTime; }
生成的DDL如下:
create table audit_record_ ( id bigint not null auto_increment comment 'ID主键', audit_type varchar(30) comment '审计类型', creation_time datetime comment '创建时间', description longtext comment '操作简述', user_id bigint comment '操作人', primary key (id) ) comment='系统审计表';
主要代码片断如下:
import java.io.IOException; import java.util.Properties; import javax.persistence.Embeddable; import javax.persistence.Entity; import javax.persistence.MappedSuperclass; import org.hibernate.MappingException; import org.hibernate.cfg.MyConfiguration; import org.hibernate.tool.hbm2ddl.SchemaExport; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.TypeFilter; import org.springframework.util.ClassUtils; public class SchemaExportTool extends MyConfiguration { private static final long serialVersionUID = 1L; private static final String RESOURCE_PATTERN = "/**/*.class"; private static final String PACKAGE_INFO_SUFFIX = ".package-info"; private static final TypeFilter[] ENTITY_TYPE_FILTERS = new TypeFilter[] { new AnnotationTypeFilter(Entity.class, false), new AnnotationTypeFilter(Embeddable.class, false), new AnnotationTypeFilter(MappedSuperclass.class, false) }; private final ResourcePatternResolver resourcePatternResolver; public SchemaExportTool() { this.resourcePatternResolver = ResourcePatternUtils .getResourcePatternResolver(new PathMatchingResourcePatternResolver()); } public static void main(String[] args) { try { Properties p = new Properties(); p.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect"); SchemaExportTool cfg = new SchemaExportTool(); cfg.addProperties(p); cfg.setNamingStrategy(new ImprovedMyNamingStrategy()); cfg.scanPackage("com.share.passport.domain", "com.share.authority.domain", "com.share.utils.domain"); SchemaExport se = new SchemaExport(cfg); if (null != args && args.length > 1) if ("-f".equals(args[0])) se.setOutputFile(args[1]); else se.setOutputFile("create_table.sql"); else se.setOutputFile("create_table.sql"); se.setDelimiter(";"); // se.drop(false, false); se.create(false, false); } catch (Exception e) { e.printStackTrace(); } } private SchemaExportTool scanPackage(String... packagesToScan) { try { for (String pkg : packagesToScan) { String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN; Resource[] resources = this.resourcePatternResolver .getResources(pattern); MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory( this.resourcePatternResolver); for (Resource resource : resources) { if (resource.isReadable()) { MetadataReader reader = readerFactory .getMetadataReader(resource); String className = reader.getClassMetadata() .getClassName(); if (matchesEntityTypeFilter(reader, readerFactory)) { addAnnotatedClass(this.resourcePatternResolver .getClassLoader().loadClass(className)); } else if (className.endsWith(PACKAGE_INFO_SUFFIX)) { addPackage(className.substring( 0, className.length() - PACKAGE_INFO_SUFFIX.length())); } } } } return this; } catch (IOException ex) { throw new MappingException( "Failed to scan classpath for unlisted classes", ex); } catch (ClassNotFoundException ex) { throw new MappingException( "Failed to load annotated classes from classpath", ex); } } /** * Check whether any of the configured entity type filters matches the * current class descriptor contained in the metadata reader. */ private boolean matchesEntityTypeFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException { for (TypeFilter filter : ENTITY_TYPE_FILTERS) { if (filter.match(reader, readerFactory)) { return true; } } return false; } }
/** * $Id:$ */ package com.share.utils.hibernate; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.cfg.Ejb3Column; import org.hibernate.mapping.PersistentClass; import com.share.annotations.Comment; public class CommentBinder { public static void bindTableComment(XClass clazzToProcess, PersistentClass persistentClass) { if (clazzToProcess.isAnnotationPresent(Comment.class)) { String tableComment = clazzToProcess.getAnnotation(Comment.class).value(); persistentClass.getTable().setComment(tableComment); } } public static void bindColumnComment(XProperty property, Ejb3Column[] columns) { if (null != columns) if (property.isAnnotationPresent(Comment.class)) { String comment = property.getAnnotation(Comment.class).value(); for (Ejb3Column column : columns) { column.getMappingColumn().setComment(comment); } } } public static void bindColumnComment(XProperty property, Ejb3Column column) { if (null != column) if (property.isAnnotationPresent(Comment.class)) { String comment = property.getAnnotation(Comment.class).value(); column.getMappingColumn().setComment(comment); } } }
/** * $Id:$ */ package com.share.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Comment { String value() default ""; }