Skip to content
This repository has been archived by the owner on Jan 19, 2022. It is now read-only.

Commit

Permalink
fix for GRAILS-2759 "Can the tablePerSubclass mapping definition be e…
Browse files Browse the repository at this point in the history
…xtended to allow table per concrete class"
  • Loading branch information
graemerocher committed Jul 23, 2013
1 parent 995388e commit 7f98006
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 5 deletions.
Expand Up @@ -48,6 +48,8 @@
import org.codehaus.groovy.grails.plugins.GrailsPlugin;
import org.codehaus.groovy.grails.plugins.GrailsPluginManager;
import org.codehaus.groovy.grails.validation.ConstrainedProperty;
import org.dom4j.Attribute;
import org.dom4j.Element;
import org.hibernate.FetchMode;
import org.hibernate.MappingException;
import org.hibernate.cfg.BinderHelper;
Expand Down Expand Up @@ -77,9 +79,11 @@
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.SingleTableSubclass;
import org.hibernate.mapping.Subclass;
import org.hibernate.mapping.UnionSubclass;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UniqueKey;
import org.hibernate.mapping.Value;
import org.hibernate.persister.entity.UnionSubclassEntityPersister;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.IntegerType;
import org.hibernate.type.LongType;
Expand Down Expand Up @@ -1497,10 +1501,15 @@ protected void bindSubClass(GrailsDomainClass sub, PersistentClass parent,
evaluateMapping(sub);
Mapping m = getMapping(parent.getMappedClass());
Subclass subClass;
boolean tablePerSubclass = m != null && !m.getTablePerHierarchy();
boolean tablePerSubclass = m != null && !m.getTablePerHierarchy() && !m.isTablePerConcreteClass();
boolean tablePerConcreteClass = m != null && m.isTablePerConcreteClass();
if (tablePerSubclass) {
subClass = new JoinedSubclass(parent);
} else {
}
else if(tablePerConcreteClass) {
subClass = new UnionSubclass(parent);
}
else {
subClass = new SingleTableSubclass(parent);
// set the descriminator value as the name of the class. This is the
// value used by Hibernate to decide what the type of the class is
Expand All @@ -1521,6 +1530,9 @@ protected void bindSubClass(GrailsDomainClass sub, PersistentClass parent,
if (tablePerSubclass) {
bindJoinedSubClass(sub, (JoinedSubclass) subClass, mappings, m, sessionFactoryBeanName);
}
else if( tablePerConcreteClass) {
bindUnionSubclass(sub, (UnionSubclass) subClass, mappings, sessionFactoryBeanName);
}
else {
bindSubClass(sub, subClass, mappings, sessionFactoryBeanName);
}
Expand All @@ -1531,6 +1543,44 @@ protected void bindSubClass(GrailsDomainClass sub, PersistentClass parent,
}
}


public void bindUnionSubclass(GrailsDomainClass subClass, UnionSubclass unionSubclass,
Mappings mappings, String sessionFactoryBeanName) throws MappingException {


Mapping subMapping = getMapping(subClass.getClazz());

if ( unionSubclass.getEntityPersisterClass() == null ) {
unionSubclass.getRootClass().setEntityPersisterClass(
UnionSubclassEntityPersister.class );
}

String schema = subMapping != null && subMapping.getTable().getSchema() != null ?
subMapping.getTable().getSchema() : null;

String catalog = subMapping != null && subMapping.getTable().getCatalog() != null ?
subMapping.getTable().getCatalog() : null;

Table denormalizedSuperTable = unionSubclass.getSuperclass().getTable();
Table mytable = mappings.addDenormalizedTable(
schema,
catalog,
getTableName(subClass, sessionFactoryBeanName),
unionSubclass.isAbstract() != null && unionSubclass.isAbstract(),
null,
denormalizedSuperTable
);
unionSubclass.setTable( mytable );
unionSubclass.setClassName(subClass.getFullName());

LOG.info(
"Mapping union-subclass: " + unionSubclass.getEntityName() +
" -> " + unionSubclass.getTable().getName()
);

createClassProperties(subClass, unionSubclass, mappings, sessionFactoryBeanName);

}
/**
* Binds a joined sub-class mapping using table-per-subclass
*
Expand Down Expand Up @@ -2384,6 +2434,9 @@ protected void bindVersion(GrailsDomainClassProperty version, RootClass entity,
protected void bindSimpleId(GrailsDomainClassProperty identifier, RootClass entity,
Mappings mappings, Identity mappedId, String sessionFactoryBeanName) {

Mapping mapping = getMapping(identifier.getDomainClass());
boolean useSequence = mapping != null && mapping.isTablePerConcreteClass();

// create the id value
SimpleValue id = new SimpleValue(mappings, entity.getTable());
// set identifier on entity
Expand All @@ -2393,11 +2446,15 @@ protected void bindSimpleId(GrailsDomainClassProperty identifier, RootClass enti

if (mappedId == null) {
// configure generator strategy
id.setIdentifierGeneratorStrategy("native");
id.setIdentifierGeneratorStrategy(useSequence ? "sequence" : "native");
} else {
id.setIdentifierGeneratorStrategy(mappedId.getGenerator());
String generator = mappedId.getGenerator();
if("native".equals(generator) && useSequence) {
generator = "sequence";
}
id.setIdentifierGeneratorStrategy(generator);
params.putAll(mappedId.getParams());
if ("assigned".equals(mappedId.getGenerator())) {
if ("assigned".equals(generator)) {
id.setNullValue("undefined");
}
}
Expand Down
Expand Up @@ -305,6 +305,15 @@ class HibernateMappingBuilder {
mapping.tablePerHierarchy = !isTablePerSubClass
}

/**
* If true the class and its subclasses will be mapped with table per subclass mapping
*/
void tablePerConcreteClass(boolean isTablePerConcreteClass) {
mapping.tablePerHierarchy = false
mapping.tablePerConcreteClass = true
}


/**
* <p>Configures the second-level cache with the default usage of 'read-write' and the default include of 'all' if
* the passed argument is true
Expand Down
Expand Up @@ -67,6 +67,11 @@ class Mapping {
*/
boolean tablePerHierarchy = true

/**
* Sets whether to use table-per-concrete-class or table-per-subclass mapping
*/
boolean tablePerConcreteClass = false

/**
* Sets whether automatic timestamping should occur for columns like last_updated and date_created
*/
Expand Down Expand Up @@ -149,4 +154,13 @@ class Mapping {
* DDL comment.
*/
String comment

boolean isTablePerConcreteClass() {
return tablePerConcreteClass
}

void setTablePerConcreteClass(boolean tablePerConcreteClass) {
this.tablePerHierarchy = !tablePerConcreteClass
this.tablePerConcreteClass = tablePerConcreteClass
}
}
@@ -0,0 +1,75 @@
package grails.gorm.tests

import grails.persistence.Entity
import org.hibernate.Session
import java.sql.Connection

/**
*/
class TablePerConcreteClassSpec extends GormDatastoreSpec{

void "Test that table per concrete class produces correct tables"() {
expect:"The table structure is correct"
Payment.withSession { Session session ->

Connection conn = session.connection()

conn.prepareStatement("select id, version, amount from payment").execute()
conn.prepareStatement("select id, version, amount, credit_card_type from credit_card_payment").execute()
conn.prepareStatement("select id, version, amount, currency from cash_payment").execute()
}
}

void "Test that polymorphic queries work correctly with table per concrete class"() {
given:"Some test data with subclasses"
def p = new Payment(amount: 1.1)
p.save()
new CreditCardPayment(creditCardType: 'mastercard', amount: 110).save()
new CreditCardPayment(creditCardType: 'amex', amount: 115).save()
new CashPayment(currency: Currency.getInstance("USD"), amount: 50).save(flush:true)
session.clear()

expect:"The correct results from queries"
Payment.count() == 4
CreditCardPayment.count() == 2
CashPayment.count() == 1

when:"The subclasses are loaded"
List<CreditCardPayment> creditCardPayments = CreditCardPayment.list()
List<CashPayment> cashPayments = CashPayment.list()
List<Payment> allPayments = Payment.list()

then:"The results are correct"
creditCardPayments.size() == 2
creditCardPayments[0].creditCardType == 'mastercard'
creditCardPayments[0].amount== 110
creditCardPayments[1].creditCardType == 'amex'
creditCardPayments[1].amount== 115
cashPayments.size() == 1
cashPayments[0].currency == Currency.getInstance("USD")
allPayments.size() == 4

}

@Override
List getDomainClasses() {
[Payment, CreditCardPayment, CashPayment]
}
}

@Entity
class Payment {
Long id
Long version
Double amount

static mapping = {
tablePerConcreteClass true
}
}
class CreditCardPayment extends Payment {
String creditCardType
}
class CashPayment extends Payment {
Currency currency
}
Expand Up @@ -125,6 +125,7 @@ class OptLockVersioned implements Serializable {
@Entity
class OptLockNotVersioned implements Serializable {
Long id
Long version

String name

Expand Down

0 comments on commit 7f98006

Please sign in to comment.