1. 程式人生 > >讓Hibernate生成的DDL指令碼自動增加註釋

讓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上測試成功,因為程式碼只是在開發期執行,不需要良好的結構和優化,所以只是簡單實現了,需要此功能的朋友可以自己實現。

以下是處理結果示例:

[java] view plaincopyprint?
  1. @Entity
  2. @Table(name = "AuditRecord_")  
  3. @Comment("系統審計表")  
  4. @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)  
  5. publicclass AuditRecord implements Serializable {  
  6. privatestaticfinallong serialVersionUID = 8844543936912679937L;  
  7. @Id
  8. @GeneratedValue
  9. @Comment
    ("ID主鍵")  
  10. private Long id;  
  11. @Comment("審計型別")  
  12. @Column(length = 30)  
  13. private String auditType;  
  14. @Comment("操作人")  
  15. @ManyToOne
  16. @JoinColumn(name = "userId")  
  17. private Passport operator;  
  18. // 操作簡述
  19. @Comment("操作簡述")  
  20. @Column(length = 4000)  
  21. private String description;  
  22. @Comment("建立時間")  
  23. private Date creationTime;  
  24. }  
@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如下:

[sql] view plaincopyprint?
  1. createtable audit_record_ (  
  2.         id bigintnotnull auto_increment comment 'ID主鍵',  
  3.         audit_type varchar(30) comment '審計型別',  
  4.         creation_time datetime comment '建立時間',  
  5.         description longtext comment '操作簡述',  
  6.         user_id bigint comment '操作人',  
  7. primarykey (id)  
  8.     ) comment='系統審計表';  
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='系統審計表';

主要程式碼片斷如下:

[java] view plaincopyprint?
  1. import java.io.IOException;  
  2. import java.util.Properties;  
  3. import javax.persistence.Embeddable;  
  4. import javax.persistence.Entity;  
  5. import javax.persistence.MappedSuperclass;  
  6. import org.hibernate.MappingException;  
  7. import org.hibernate.cfg.MyConfiguration;  
  8. import org.hibernate.tool.hbm2ddl.SchemaExport;  
  9. import org.springframework.core.io.Resource;  
  10. import org.springframework.core.io.support.PathMatchingResourcePatternResolver;  
  11. import org.springframework.core.io.support.ResourcePatternResolver;  
  12. import org.springframework.core.io.support.ResourcePatternUtils;  
  13. import org.springframework.core.type.classreading.CachingMetadataReaderFactory;  
  14. import org.springframework.core.type.classreading.MetadataReader;  
  15. import org.springframework.core.type.classreading.MetadataReaderFactory;  
  16. import org.springframework.core.type.filter.AnnotationTypeFilter;  
  17. import org.springframework.core.type.filter.TypeFilter;  
  18. import org.springframework.util.ClassUtils;  
  19. publicclass SchemaExportTool extends MyConfiguration {  
  20. privatestaticfinallong serialVersionUID = 1L;  
  21. privatestaticfinal String RESOURCE_PATTERN = "/**/*.class";  
  22. privatestaticfinal String PACKAGE_INFO_SUFFIX = ".package-info";  
  23. privatestaticfinal TypeFilter[] ENTITY_TYPE_FILTERS = new TypeFilter[] {  
  24. new AnnotationTypeFilter(Entity.classfalse),  
  25. new AnnotationTypeFilter(Embeddable.classfalse),  
  26. new AnnotationTypeFilter(MappedSuperclass.classfalse) };  
  27. privatefinal ResourcePatternResolver resourcePatternResolver;  
  28. public SchemaExportTool() {  
  29. this.resourcePatternResolver = ResourcePatternUtils  
  30.                 .getResourcePatternResolver(new PathMatchingResourcePatternResolver());  
  31.     }  
  32. publicstaticvoid main(String[] args) {  
  33. try {  
  34.             Properties p = new Properties();  
  35.             p.setProperty("hibernate.dialect",  
  36. "org.hibernate.dialect.MySQLDialect");  
  37.             SchemaExportTool cfg = new SchemaExportTool();  
  38.             cfg.addProperties(p);  
  39.             cfg.setNamingStrategy(new ImprovedMyNamingStrategy());  
  40.             cfg.scanPackage("com.share.passport.domain",  
  41. "com.share.authority.domain""com.share.utils.domain");  
  42.             SchemaExport se = new SchemaExport(cfg);  
  43. if (null != args && args.length > 1)  
  44. if ("-f".equals(args[0]))  
  45.                     se.setOutputFile(args[1]);  
  46. else
  47.                     se.setOutputFile("create_table.sql");  
  48. else
  49.                 se.setOutputFile("create_table.sql");  
  50.             se.setDelimiter(";");  
  51. // se.drop(false, false);
  52.             se.create(falsefalse);  
  53.         } catch (Exception e) {  
  54.             e.printStackTrace();  
  55.         }  
  56.     }  
  57. private SchemaExportTool scanPackage(String... packagesToScan) {  
  58. try {  
  59. for (String pkg : packagesToScan) {  
  60.                 String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX  
  61.                         + ClassUtils.convertClassNameToResourcePath(pkg)  
  62.                         + RESOURCE_PATTERN;  
  63.                 Resource[] resources = this.resourcePatternResolver  
  64.                         .getResources(pattern);  
  65.                 MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(  
  66. this.resourcePatternResolver);  
  67. for (Resource resource : resources) {  
  68. if (resource.isReadable()) {  
  69.                         MetadataReader reader = readerFactory  
  70.                                 .getMetadataReader(resource);  
  71.                         String className = reader.getClassMetadata()  
  72.                                 .getClassName();  
  73. if (matchesEntityTypeFilter(reader, readerFactory)) {  
  74.                             addAnnotatedClass(this.resourcePatternResolver  
  75.                                     .getClassLoader().loadClass(className));  
  76.                         } elseif (className.endsWith(PACKAGE_INFO_SUFFIX)) {  
  77.                             addPackage(className.substring(  
  78. 0,  
  79.                                     className.length()  
  80.                                             - PACKAGE_INFO_SUFFIX.length()));  
  81.                         }  
  82.                     }  
  83.                 }  
  84.             }  
  85. returnthis;  
  86.         } catch (IOException ex) {  
  87. thrownew MappingException(  
  88. "Failed to scan classpath for unlisted classes", ex);  
  89.         } catch (ClassNotFoundException ex) {  
  90. thrownew MappingException(  
  91. "Failed to load annotated classes from classpath", ex);  
  92.         }  
  93.     }  
  94. /** 
  95.      * Check whether any of the configured entity type filters matches the 
  96.      * current class descriptor contained in the metadata reader. 
  97.      */
  98. privateboolean matchesEntityTypeFilter(MetadataReader reader,  
  99.             MetadataReaderFactory readerFactory) throws IOException {  
  100. for (TypeFilter filter : ENTITY_TYPE_FILTERS) {  
  101. if (filter.match(reader, readerFactory)) {  
  102. returntrue;  
  103.             }  
  104.         }  
  105. returnfalse;  
  106.     }  
  107. }  
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;
	}

}
[java] view plaincopyprint?
  1. /** 
  2.  * $Id:$ 
  3.  */
  4. package com.share.utils.hibernate;  
  5. import org.hibernate.annotations.common.reflection.XClass;  
  6. import org.hibernate.annotations.common.reflection.XProperty;  
  7. import org.hibernate.cfg.Ejb3Column;  
  8. import org.hibernate.mapping.PersistentClass;  
  9. import com.share.annotations.Comment;  
  10. publicclass CommentBinder {  
  11. publicstaticvoid bindTableComment(XClass clazzToProcess, PersistentClass persistentClass) {  
  12. if (clazzToProcess.isAnnotationPresent(Comment.class)) {  
  13.             String tableComment = clazzToProcess.getAnnotation(Comment.class).value();  
  14.             persistentClass.getTable().setComment(tableComment);  
  15.         }  
  16.     }  
  17. publicstaticvoid bindColumnComment(XProperty property, Ejb3Column[] columns) {  
  18. if (null != columns)  
  19. if (property.isAnnotationPresent(Comment.class)) {  
  20.                 String comment = property.getAnnotation(Comment.class).value();  
  21. for (Ejb3Column column : columns) {  
  22.                     column.getMappingColumn().setComment(comment);  
  23.                 }  
  24.             }  
  25.     }  
  26. publicstaticvoid bindColumnComment(XProperty property, Ejb3Column column) {  
  27. if (null != column)  
  28. if (property.isAnnotationPresent(Comment.class)) {  
  29.                 String comment = property.getAnnotation(Comment.class).value();  
  30.                 column.getMappingColumn().setComment(comment);  
  31.             }  
  32.     }  
  33. }  
/**
 * $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);

            }
    }
}
[java] view plaincopyprint?
  1. /** 
  2.  * $Id:$ 
  3.  */
  4. package com.share.annotations;  
  5. import java.lang.annotation.ElementType;  
  6. import java.lang.annotation.Retention;  
  7. import java.lang.annotation.RetentionPolicy;  
  8. import java.lang.annotation.Target;  
  9. @Target({ElementType.TYPE,ElementType.FIELD})  
  10. @Retention(RetentionPolicy.RUNTIME)  
  11. public@interface Comment {  
  12.     String value() default"";  
  13. }  
/**
 * $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 "";
}