支持将JPA批注(例如@Entity
)添加到2.0中grails-app/domain
Groovy类中的问题已得到解决。 这是由于对使用AST转换将大多数GORM方法添加到域类字节码而不是在运行时通过元编程将它们添加到元类所做的更改。 有一种解决方法–将类放在src/groovy
(或将它们写在Java中,然后将它们放在src/java
)。
尽管这会增加维护的麻烦,因为通过在grails-app/domain
中会自动发现类,但是不会扫描src/groovy
或src/java
中带注释的类,因此必须在grails-app/conf/hibernate/hibernate.cfg.xml
明确列出它们grails-app/conf/hibernate/hibernate.cfg.xml
。 我们做注释Groovy和Java类的Spring bean注释的能力等类似的支持的东西@Component
并有一个可选属性grails.spring.bean.packages
在Config.groovy
,可以包含一个或多个包名称进行搜索。 我们配置一个Spring扫描器来查找带注释的类,并自动将它们注册为bean。 这就是我们为JPA注释的src/groovy
和src/java
类所需要的。
事实证明,有一个Spring类可以做到这一点, org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean
。 它扩展了标准的SessionFactory
工厂bean类org.springframework.orm.hibernate3.LocalSessionFactoryBean
并添加了对要使用的类名的显式列表以及要扫描的包的列表的支持。 不幸的是,Grails工厂bean类org.codehaus.groovy.grails.orm.hibernate.ConfigurableLocalSessionFactoryBean
也扩展了LocalSessionFactoryBean
因此,如果将应用程序配置为使用AnnotationSessionFactoryBean
,则会从ConfigurableLocalSessionFactoryBean
丢失很多重要功能。 因此,这是ConfigurableLocalSessionFactoryBean
的子类,该子类借鉴了AnnotationSessionFactoryBean
的有用注释支持,并且可以在Grails应用程序中使用:
package com.burtbeckwith.grails.jpa;
import java.io.IOException;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.MappedSuperclass;
import org.codehaus.groovy.grails.orm.hibernate.ConfigurableLocalSessionFactoryBean;
import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.cfg.Configuration;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
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;
/**
* Based on org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean.
* @author Burt Beckwith
*/
public class AnnotationConfigurableLocalSessionFactoryBean extends ConfigurableLocalSessionFactoryBean implements ResourceLoaderAware {
private static final String RESOURCE_PATTERN = '/**/*.class';
private Class<?>[] annotatedClasses;
private String[] annotatedPackages;
private String[] packagesToScan;
private TypeFilter[] entityTypeFilters = new TypeFilter[] {
new AnnotationTypeFilter(Entity.class, false),
new AnnotationTypeFilter(Embeddable.class, false),
new AnnotationTypeFilter(MappedSuperclass.class, false),
new AnnotationTypeFilter(org.hibernate.annotations.Entity.class, false)};
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
public AnnotationConfigurableLocalSessionFactoryBean() {
setConfigurationClass(GrailsAnnotationConfiguration.class);
}
public void setAnnotatedClasses(Class<?>[] annotatedClasses) {
this.annotatedClasses = annotatedClasses;
}
public void setAnnotatedPackages(String[] annotatedPackages) {
this.annotatedPackages = annotatedPackages;
}
public void setPackagesToScan(String[] packagesToScan) {
this.packagesToScan = packagesToScan;
}
public void setEntityTypeFilters(TypeFilter[] entityTypeFilters) {
this.entityTypeFilters = entityTypeFilters;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
}
@Override
protected void postProcessMappings(Configuration config) throws HibernateException {
GrailsAnnotationConfiguration annConfig = (GrailsAnnotationConfiguration)config;
if (annotatedClasses != null) {
for (Class<?> annotatedClass : annotatedClasses) {
annConfig.addAnnotatedClass(annotatedClass);
}
}
if (annotatedPackages != null) {
for (String annotatedPackage : annotatedPackages) {
annConfig.addPackage(annotatedPackage);
}
}
scanPackages(annConfig);
}
protected void scanPackages(GrailsAnnotationConfiguration config) {
if (packagesToScan == null) {
return;
}
try {
for (String pkg : packagesToScan) {
logger.debug('Scanning package '' + pkg + ''');
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN;
Resource[] resources = resourcePatternResolver.getResources(pattern);
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
for (Resource resource : resources) {
if (resource.isReadable()) {
MetadataReader reader = readerFactory.getMetadataReader(resource);
String className = reader.getClassMetadata().getClassName();
if (matchesFilter(reader, readerFactory)) {
config.addAnnotatedClass(resourcePatternResolver.getClassLoader().loadClass(className));
logger.debug('Adding annotated class '' + className + ''');
}
}
}
}
}
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);
}
}
private boolean matchesFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException {
if (entityTypeFilters != null) {
for (TypeFilter filter : entityTypeFilters) {
if (filter.match(reader, readerFactory)) {
return true;
}
}
}
return false;
}
}
您可以使用与Grails注册的名称相同的名称替换应用程序的grails-app/conf/spring/resources.groovy
的Grails SessionFactory
bean:
import com.burtbeckwith.grails.jpa.AnnotationConfigurableLocalSessionFactoryBean
beans = {
sessionFactory(AnnotationConfigurableLocalSessionFactoryBean) { bean ->
bean.parent = 'abstractSessionFactoryBeanConfig'
packagesToScan = ['com.mycompany.myapp.entity']
}
}
在这里,我在packagesToScan
属性中列出了一个程序包名称,但是您可以根据需要列出任意多个。 您还可以使用annotatedClasses
属性显式列出类。 请注意,这是针对“默认”数据源的; 如果您使用多个数据源,则需要为每个数据源执行此操作。
因此,这意味着我们可以在src/groovy/com/mycompany/myapp/entity/Person.groovy
定义此类:
package com.mycompany.myapp.entity
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id
import javax.persistence.Version
@Entity
class Person {
@Id @GeneratedValue
Long id
@Version
@Column(nullable=false)
Long version
@Column(name='first', nullable=false)
String firstName
@Column(name='last', nullable=false)
String lastName
@Column(nullable=true)
String initial
@Column(nullable=false, unique=true, length=200)
String email
}
它将被检测为域类,如果您运行schema-export脚本,则表DDL将位于target/ddl.sql
。
但是,有几个问题需要注意,主要围绕约束。 您不能在类中定义constraints
或mapping
块-它们将被忽略。 您将要添加的映射只需要放入批注中即可。 例如,在上面的示例中,我覆盖了firstName
和lastName
属性的默认名称。 但是nullable=true
是JPA的默认设置,而在Grails中则相反—默认情况下,属性是必需的。 因此,尽管注释会影响数据库架构,但Grails不会使用注释中的约束,如果您无法为initial
属性提供值,则将收到此类的验证错误。
您可以通过在src/java
创建约束文件来解决此问题; 有关更多详细信息,请参阅文档 。 因此,在这种情况下,我将使用非静态constraints
属性创建src/java/com/mycompany/myapp/entity/PersonConstraints.groovy
,例如
constraints = {
initial(nullable: true)
email unique: true, length: 200)
}
这样Grails约束和数据库约束是同步的。 没有这个,我将能够创建一个域类的实例,该实例具有一个包含200个以上字符的电子邮件,它将进行验证,但是在插入行时会导致数据库约束异常。
这还具有让您使用与JPA约束(例如email
和blank
不对应的Grails约束的好处。
参考:我们的JCG合作伙伴 Burt Beckwith在An Solipsists博客上自动发现了Grails中JPA注释的域类 。
翻译自: https://www.javacodegeeks.com/2012/10/grails-autodiscovery-of-jpa-annotated-domain-classes.html