I'm currently upgrading a Grails 4.0.1 application to Grails 4.0.11 and I'm running into an issue with the join tables for one of my domain classes.
My Account
class has multiple hasMany
relationships with my class CompletionStatus
:
import grails.compiler.GrailsCompileStatic
import grails.gorm.hibernate.mapping.MappingBuilder
@GrailsCompileStatic
class Account {
String name
static hasMany = [successCompletionStatuses: CompletionStatus, noShowCompletionStatuses: CompletionStatus,
partialSegmentAttendanceCompletionStatuses: CompletionStatus]
// Have distinct join tables for the CompletionStatus associations
// https://github.com/grails/grails-core/issues/10811#issuecomment-337931115
static mapping = MappingBuilder.orm {
property('successCompletionStatuses') {
joinTable { name "account_success_completion_statuses" }
}
property('noShowCompletionStatuses') {
joinTable { name "account_no_show_completion_statuses" }
}
property('partialSegmentAttendanceCompletionStatuses') {
joinTable {
name "account_partial_completion_statuses"
key {
name "account_partial_completion_statuses_id"
}
column {
name "different_completion_status_id"
}
}
}
}
static constraints = {
name blank: false
}
}
import grails.compiler.GrailsCompileStatic
@GrailsCompileStatic
class CompletionStatus {
String courseType
String status
static constraints = {
courseType nullable: true
status blank: false
}
}
The mapping in Account
works as expected in Grails 4.0.1, with multiple join tables being created in the database. However, when running the application in Grails 4.0.11 I get an error at startup which I eventually discovered can be prevented by removing the multiple hasMany relationships from Account
:
Caused by: java.lang.NullPointerException: null
at org.hibernate.mapping.Column.getCanonicalName(Column.java:362)
at org.hibernate.mapping.Table.getColumn(Table.java:233)
at org.hibernate.mapping.Table.addColumn(Table.java:257)
at org.grails.orm.hibernate.cfg.GrailsDomainBinder.bindSimpleValue(GrailsDomainBinder.java:2938)
at org.grails.orm.hibernate.cfg.GrailsDomainBinder.bindCollectionSecondPass(GrailsDomainBinder.java:450)
at org.grails.orm.hibernate.cfg.GrailsDomainBinder$GrailsCollectionSecondPass.doSecondPass(GrailsDomainBinder.java:3406)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1693)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1661)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:286)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.build(MetadataBuildingProcess.java:83)
at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:473)
at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:84)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:689)
at org.grails.orm.hibernate.cfg.HibernateMappingContextConfiguration.buildSessionFactory(HibernateMappingContextConfiguration.java:287)
at org.grails.orm.hibernate.connections.HibernateConnectionSourceFactory.create(HibernateConnectionSourceFactory.java:86)
at org.grails.orm.hibernate.connections.AbstractHibernateConnectionSourceFactory.create(AbstractHibernateConnectionSourceFactory.java:39)
at org.grails.orm.hibernate.connections.AbstractHibernateConnectionSourceFactory.create(AbstractHibernateConnectionSourceFactory.java:23)
at org.grails.datastore.mapping.core.connections.AbstractConnectionSourceFactory.create(AbstractConnectionSourceFactory.java:64)
at org.grails.datastore.mapping.core.connections.AbstractConnectionSourceFactory.create(AbstractConnectionSourceFactory.java:52)
at org.grails.datastore.mapping.core.connections.ConnectionSourcesInitializer.create(ConnectionSourcesInitializer.groovy:24)
at org.grails.orm.hibernate.HibernateDatastore.<init>(HibernateDatastore.java:212)
at sun.reflect.GeneratedConstructorAccessor64.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:175)
... 123 common frames omitted
I found I was able to get the database tables account_success_completion_statuses
and account_no_show_completion_statuses
created by specifying the mapping in Acccount
like so, but I am still unable to customize the table name and column names for account_partial_completion_statuses
:
static mapping = MappingBuilder.orm {
property('successCompletionStatuses') {
joinTable "account_success_completion_statuses"
}
property('noShowCompletionStatuses') {
joinTable "account_no_show_completion_statuses"
}
property('partialSegmentAttendanceCompletionStatuses') {
joinTable {
name "account_partial_completion_statuses"
key "account_partial_completion_statuses_id"
// This isn't setting the column name
column "different_completion_status_id"
// This isn't setting the column name either
// column { name "different_completion_status_id" }
}
}
}
With the mapping above, a database table named account_completion_status
is created instead of account_partial_completion_statuses
, and it has columns account_partial_completion_statuses_id
and completion_status_id
.
For more context in case it is needed, I'm using MySQL 5.6.51 and the library mysql:mysql-connector-java:5.1.48
in my application.
I found the solution in the Grails documentation on joinTable!
The join tables are created properly in the database and the application runs fine when I specify my Account
class like so:
import grails.compiler.GrailsCompileStatic
@GrailsCompileStatic
class Account {
String name
static hasMany = [successCompletionStatuses: CompletionStatus,
noShowCompletionStatuses: CompletionStatus,
partialSegmentAttendanceCompletionStatuses: CompletionStatus]
// Have distinct join tables for the CompletionStatus associations
// https://docs.grails.org/3.1.1/ref/Database%20Mapping/joinTable.html
static mapping = {
successCompletionStatuses joinTable: [
name: 'account_success_completion_statuses']
noShowCompletionStatuses joinTable: [
name: 'account_no_show_completion_statuses']
partialSegmentAttendanceCompletionStatuses joinTable: [
name: 'account_partial_completion_statuses',
key: 'account_partial_completion_statuses_id',
column: 'different_completion_status_id']
}
static constraints = {
name blank: false
}
}