|
|
|
Matthew Adams
|
Hi all,
NB: All source for this example is attached and is pasted at the end of this email, in case the attachment doesn't survive the mailing list. I've also entered the following JIRA to track this issue: https://issues.apache.org/jira/browse/OPENJPA-1361 Synopsis: bidirectional one-to-many relationship where the one side references the objects on the many side using a concrete class reference and the many side references the object on the one side using an interface reference. When specifying the mappedBy property of the @OneToMany annotation, OpenJPA throws the error: Collection field "example.model.Concrete.pebbles" declares that it is mapped by "example.model.Pebble.buildingMaterial", but this is not a valid inverse relation. When I remove the mappedBy property, everything works fine, except that OpenJPA creates an extra join table (Concrete_Pebble) where it shouldn't, IMHO. I have two questions, an answer to either one of which will suffice: 1. Why can't I use mappedBy in this scenario? 2. How do I annotate things with @JoinColumn or similar that will allow me to not have a join table and only the reference from Pebble back to its BuildingMaterial? Thanks, Matthew ==================== package example.model; import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Version; @Entity public class Concrete implements BuildingMaterial { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private Long assignedId; @Version @Column(name = "version") private long assignedVersion; @OneToMany(cascade = { CascadeType.MERGE, CascadeType.REFRESH, CascadeType.PERSIST }, fetch = FetchType.LAZY) public Set<Pebble> pebbles = new HashSet<Pebble>(); public Long getId() { return assignedId; } public long getVersion() { return assignedVersion; } public Discriminator getDiscriminator() { return Discriminator.CONCRETE; } public void add(Pebble pebble) { this.pebbles.add(pebble); pebble.buildingMaterial = this; } public Set<Pebble> getPebbles() { return new HashSet<Pebble>(pebbles); } } =========================== package example.model; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.persistence.Version; import org.apache.openjpa.persistence.jdbc.Strategy; @Entity public class Pebble implements PersistentlyIdentifiable { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private Long assignedId; @Version @Column(name = "version") private long assignedVersion; @ManyToOne(cascade = { CascadeType.MERGE, CascadeType.REFRESH, CascadeType.PERSIST }, fetch = FetchType.LAZY) @Strategy("example.openjpa.PersistentInterfaceValueHandler") protected BuildingMaterial buildingMaterial; public Long getId() { return assignedId; } public long getVersion() { return assignedVersion; } public void setBuildingMaterial(BuildingMaterial buildingMaterial) { buildingMaterial.add(this); } public Discriminator getDiscriminator() { return Discriminator.PEBBLE; } public BuildingMaterial getBuildingMaterial() { return buildingMaterial; } } ============================ package example.model; public interface PersistentlyIdentifiable { Comparable<?> getId(); long getVersion(); Discriminator getDiscriminator(); } ============================ package example.model; import java.util.Set; public interface BuildingMaterial extends PersistentlyIdentifiable { void add(Pebble pebble); Set<Pebble> getPebbles(); } ============================ package example.model; import java.util.HashMap; import java.util.Map; /** * Enum used to store discriminators in the database for polymorphic references. */ public enum Discriminator { CONCRETE(Concrete.class), PEBBLE(Pebble.class); private static class X { public static final Map<String, Discriminator> discriminatorsByClassName = new HashMap<String, Discriminator>(); public static void storeByClassName(Discriminator discriminator) { synchronized (discriminatorsByClassName) { discriminatorsByClassName.put(discriminator.getClassName(), discriminator); } } } private Discriminator(Class<?> clazz) { this.className = clazz.getName(); X.storeByClassName(this); } private String className; public String getClassName() { return className; } public static Discriminator getDiscriminatorEnum(String className) { return X.discriminatorsByClassName.get(className); } public static String getDiscriminatorName(Class<?> clazz) { Discriminator d = getDiscriminatorEnum(clazz.getName()); if (d == null) { throw new IllegalArgumentException("Class " + clazz.getName() + " does not have a corresponding Discriminator"); } return d.name(); } public static Class<?> getDiscriminatorClassFor(String name) throws ClassNotFoundException { return getDiscriminatorClassFor(name, Discriminator.class .getClassLoader()); } public static Class<?> getDiscriminatorClassFor(String name, ClassLoader loader) throws ClassNotFoundException { return Class.forName(Discriminator.valueOf(name).getClassName(), true, loader); } } =========================== package example.openjpa; import java.sql.SQLException; import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration; import org.apache.openjpa.jdbc.kernel.JDBCStore; import org.apache.openjpa.jdbc.meta.ValueMapping; import org.apache.openjpa.jdbc.meta.strats.AbstractValueHandler; import org.apache.openjpa.jdbc.schema.Column; import org.apache.openjpa.jdbc.schema.ColumnIO; import org.apache.openjpa.kernel.OpenJPAStateManager; import org.apache.openjpa.kernel.StoreContext; import org.apache.openjpa.meta.JavaTypes; import org.apache.openjpa.util.StoreException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import example.model.Discriminator; import example.model.PersistentlyIdentifiable; @SuppressWarnings("serial") public class PersistentInterfaceValueHandler extends AbstractValueHandler { Logger log = LoggerFactory.getLogger(PersistentInterfaceValueHandler.class); @Override public Column[] map(ValueMapping valueMapping, String fieldName, ColumnIO columnIo, boolean adapt) { Column discriminatorColumn = new Column(); discriminatorColumn.setName(fieldName.toUpperCase() + "_DISCRIMINATOR"); discriminatorColumn.setJavaType(JavaTypes.STRING); Column idColumn = new Column(); idColumn.setName(fieldName.toUpperCase() + "_ID"); idColumn.setJavaType(JavaTypes.LONG); return new Column[] { discriminatorColumn, idColumn }; } public boolean isVersionable(ValueMapping vm) { return true; } public boolean objectValueRequiresLoad(ValueMapping vm) { return true; } public Object toDataStoreValue(ValueMapping vm, Object val, JDBCStore store) { if (val == null) { return new Object[] { null, null }; } return new Object[] { deriveDiscriminator(val.getClass()), ((PersistentlyIdentifiable) val).getId() }; } public String deriveDiscriminator(Class<?> clazz) { if (log.isDebugEnabled()) { log.debug("Getting discriminator for class: [" + clazz.getName() + "]"); } String discriminator = Discriminator.getDiscriminatorName(clazz); if (log.isDebugEnabled()) { log.debug("Discriminator for class: [" + clazz.getName() + "]: [" + discriminator + "]"); } return discriminator; } public Class<?> deriveClass(String name, ClassLoader loader) throws ClassNotFoundException { if (log.isDebugEnabled()) { log.debug("Getting class for discriminator: [" + name + "]"); } Class<?> clazz = Discriminator.getDiscriminatorClassFor(name, loader); if (log.isDebugEnabled()) { log.debug("Class for discriminator: [" + name + "]: [" + clazz.getName() + "]"); } return clazz; } public Object toObjectValue(ValueMapping vm, Object val, OpenJPAStateManager sm, JDBCStore store, JDBCFetchConfiguration fetch) throws SQLException { if (val == null) { return null; } Object[] values = (Object[]) val; StoreContext ctx = store.getContext(); ClassLoader loader = store.getConfiguration() .getClassResolverInstance().getClassLoader(vm.getType(), ctx.getClassLoader()); Class<?> cls = null; String clsName = (String) values[0]; Long oidStr = (Long) values[1]; if (clsName == null) { return null; } try { cls = deriveClass(clsName, loader); } catch (ClassNotFoundException cnfe) { throw new StoreException(cnfe); } Object oid = ctx.newObjectId(cls, oidStr); return store.find(oid, vm, fetch); } } ======================== package example.test.integration; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import java.util.Set; import org.junit.Test; import org.springframework.transaction.annotation.Transactional; import example.model.BuildingMaterial; import example.model.Concrete; import example.model.Pebble; public class ConcretePebbleTest extends AbstractJpaTest { @Test @Transactional public void testConcrete() { BuildingMaterial bm = new Concrete(); em.persist(bm); Pebble p = new Pebble(); bm.add(p); em.flush(); Object bmid = bm.getId(); Object pid = p.getId(); em.clear(); // navigate from BuildingMaterials side bm = em.find(bm.getClass(), bmid); Set<Pebble> pebbles = null; assertNotNull(pebbles = bm.getPebbles()); assertEquals(1, pebbles.size()); em.clear(); // navigate from Pebble side p = em.find(Pebble.class, pid); assertNotNull(bm = p.getBuildingMaterial()); } } ======================= package example.test.integration; import javax.annotation.PostConstruct; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.PersistenceContext; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @ContextConfiguration(locations={"classpath:applicationContext.xml"}) @RunWith(SpringJUnit4ClassRunner.class) abstract public class AbstractJpaTest { @Autowired protected EntityManagerFactory emf; @PersistenceContext protected EntityManager em; @PostConstruct public void afterPropertiesSet() throws Exception { emf.createEntityManager(); } } ======================== package example.test.unit; import static org.junit.Assert.assertEquals; import org.junit.Test; import example.model.Concrete; import example.model.Discriminator; public class DiscriminatorTest { @Test public void testDiscriminator() throws ClassNotFoundException { Discriminator d = Discriminator.CONCRETE; assertEquals(Discriminator.getDiscriminatorName(Concrete.class), d .name()); assertEquals(Discriminator.getDiscriminatorClassFor(d.name()), Concrete.class); } } =========================== # log4j.properties log4j.rootLogger=ERROR, A1 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p %c %x - %m%n log4j.logger.example=debug =========================== # test.properties test.database=my-integration-test =========================== <!-- applicationContext.xml --> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:test.properties" /> </bean> <bean id="HsqldbDataSource" class="org.hsqldb.jdbc.jdbcDataSource"> <property name="user" value="sa" /> <property name="database" value="jdbc:hsqldb:mem:${test.database}" /> </bean> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceUnitName" value="case-sandbox" /> <property name="jpaProperties"> <value> openjpa.AutoDetach=commit openjpa.DetachState=fgs openjpa.QueryCache=false openjpa.jdbc.SubclassFetchMode=none openjpa.jdbc.EagerFetchMode=none openjpa.jdbc.SynchronizeMappings=buildSchema(ForeignKeys=true) </value> </property> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter"> <property name="showSql" value="${test.showSql}" /> <property name="generateDdl" value="false" /> </bean> </property> <property name="dataSource" ref="HsqldbDataSource" /> </bean> <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" /> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> <property name="jpaDialect"> <bean class="org.springframework.orm.jpa.vendor.OpenJpaDialect" /> </property> <property name="dataSource" ref="HsqldbDataSource" /> </bean> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" /> </beans> ============================= <!-- orm.xml --> <?xml version="1.0" encoding="UTF-8" ?> <entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" version="1.0"> <persistence-unit-metadata> <persistence-unit-defaults> <access>FIELD</access> <cascade-persist/> </persistence-unit-defaults> </persistence-unit-metadata> </entity-mappings> ================================ <!-- persistence.xml --> <?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0"> <persistence-unit name="case-sandbox"> <provider> org.apache.openjpa.persistence.PersistenceProviderImpl </provider> </persistence-unit> </persistence> =============================== <!-- pom.xml --> <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.mytest</groupId> <artifactId>case-sandbox</artifactId> <packaging>jar</packaging> <version>1.0.0-SNAPSHOT</version> <name>Sandbox</name> <properties> <surefire.debug.suspend>n</surefire.debug.suspend> <test.showSql>true</test.showSql> <spring.version>2.5.6</spring.version> <slf4j.version>1.5.6</slf4j.version> <jpa.spec.version>1.0</jpa.spec.version> <openjpa.version>1.2.1</openjpa.version> <hsql.version>1.8.0.1</hsql.version> </properties> <dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>javax.persistence</groupId> <artifactId>persistence-api</artifactId> <version>${jpa.spec.version}</version> </dependency> <dependency> <groupId>org.apache.openjpa</groupId> <artifactId>openjpa</artifactId> <version>${openjpa.version}</version> </dependency> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>${hsql.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> <exclusions> <exclusion> <groupId>junit</groupId> <artifactId>junit</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib-nodep</artifactId> <version>2.1_3</version> </dependency> <!-- <dependency> <groupId>org.jmock</groupId> <artifactId>jmock-junit4</artifactId> <scope>test</scope> </dependency> --> </dependencies> <build> <testResources> <testResource> <filtering>true</filtering> <directory>src/test/resources</directory> <includes> <include>**/*</include> </includes> </testResource> </testResources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.2</version> <executions> <execution> <goals> <goal>test-jar</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-antrun-plugin</artifactId> <version>1.1</version> <executions> <execution> <id>1</id> <phase>process-classes</phase> <configuration> <tasks> <path id="cp"> <path refid="maven.compile.classpath" /> </path> <pathconvert pathsep=" " property="arguments" dirsep="."> <map from="${basedir}/target/classes/" to="" /> <map from="${basedir}\target\classes\" to="" /> <mapper> <chainedmapper> <globmapper from="*.class" to="*" /> </chainedmapper> </mapper> <path id="model.path"> <fileset dir="${basedir}/target/classes/"> <include name="**/*.class" /> <exclude name="**/annotations/**/*.class" /> </fileset> </path> </pathconvert> <java classname="org.apache.openjpa.enhance.PCEnhancer" classpathref="cp" dir="target/classes" fork="true"> <arg line="${arguments} -p persistence.xml#case-sandbox" /> </java> </tasks> </configuration> <goals> <goal>run</goal> </goals> </execution> <execution> <id>2</id> <configuration> <tasks> <path id="cp"> <path refid="maven.compile.classpath" /> </path> <java classname="org.apache.openjpa.jdbc.meta.MappingTool" classpathref="cp" dir="target/classes" fork="true"> <arg line="-p persistence.xml#case-sandbox" /> <arg line="-connectionDriverName org.hsqldb.jdbc.jdbcDataSource" /> <arg line="-connectionURL jdbc:hsqldb:mem:dummy" /> <arg line="-schemaAction build" /> <arg line="-sql ../test.sql" /> </java> </tasks> </configuration> <phase>compile</phase> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.3</version> <configuration> <argLine>-Xrunjdwp:transport=dt_socket,address=9998,server=y,suspend=${surefire.debug.suspend} -Xmx512m</argLine> <includes> <include>**/*Test.java</include> </includes> <excludes> <exclude>**/integration/**</exclude> </excludes> </configuration> <executions> <execution> <id>integration-tests</id> <phase>integration-test</phase> <goals> <goal>test</goal> </goals> <configuration> <skip>false</skip> <excludes> <exclude>**/unit/**</exclude> </excludes> <includes> <include>**/integration/**/*Test.java</include> </includes> </configuration> </execution> </executions> </plugin> </plugins> </build> </project> ================================ |
||||||||||||||||
|
Michael Dick
|
Hi Matthew,
<disclosure>I haven't tried the example you submitted. </disclosure> 1. If I had to guess I'd say that it's because the interface (BuildingMaterial) doesn't have the @OneToMany annotation, and as a result it's not a valid target for the relationship. Adding the annotatation (and switching to property access instead of field access) might help here. If you can't move to property access and add the annotation then I'm not sure how you could use an interface.. Using a MappedSuperclass for BuildingMaterial instead of an interface (could be an abstract MappedSuperclass though). 2. I'd have to look into @JoinColumn, I suspect it would result in similar behavior though (I'm prepared to be wrong here - haven't used JoinColumn much). Hope this helps -mike On Fri, Oct 23, 2009 at 12:54 PM, Matthew Adams <[hidden email]>wrote: > Hi all, > > NB: All source for this example is attached and is pasted at the end > of this email, in case the attachment doesn't survive the mailing > list. I've also entered the following JIRA to track this issue: > https://issues.apache.org/jira/browse/OPENJPA-1361 > > Synopsis: bidirectional one-to-many relationship where the one side > references the objects on the many side using a concrete class > reference and the many side references the object on the one side > using an interface reference. When specifying the mappedBy property > of the @OneToMany annotation, OpenJPA throws the error: > > Collection field "example.model.Concrete.pebbles" declares that it is > mapped by "example.model.Pebble.buildingMaterial", but this is not a > valid inverse relation. > > When I remove the mappedBy property, everything works fine, except > that OpenJPA creates an extra join table (Concrete_Pebble) where it > shouldn't, IMHO. I have two questions, an answer to either one of > which will suffice: > > 1. Why can't I use mappedBy in this scenario? > 2. How do I annotate things with @JoinColumn or similar that will > allow me to not have a join table and only the reference from Pebble > back to its BuildingMaterial? > > Thanks, > Matthew > ==================== > package example.model; > > import java.util.HashSet; > import java.util.Set; > > import javax.persistence.CascadeType; > import javax.persistence.Column; > import javax.persistence.Entity; > import javax.persistence.FetchType; > import javax.persistence.GeneratedValue; > import javax.persistence.GenerationType; > import javax.persistence.Id; > import javax.persistence.OneToMany; > import javax.persistence.Version; > > @Entity > public class Concrete implements BuildingMaterial { > > @Id > @GeneratedValue(strategy = GenerationType.AUTO) > @Column(name = "id") > private Long assignedId; > > @Version > @Column(name = "version") > private long assignedVersion; > > @OneToMany(cascade = { CascadeType.MERGE, CascadeType.REFRESH, > CascadeType.PERSIST }, fetch = FetchType.LAZY) > public Set<Pebble> pebbles = new HashSet<Pebble>(); > > public Long getId() { > return assignedId; > } > > public long getVersion() { > return assignedVersion; > } > > public Discriminator getDiscriminator() { > return Discriminator.CONCRETE; > } > > public void add(Pebble pebble) { > this.pebbles.add(pebble); > pebble.buildingMaterial = this; > } > > public Set<Pebble> getPebbles() { > return new HashSet<Pebble>(pebbles); > } > } > =========================== > package example.model; > > import javax.persistence.CascadeType; > import javax.persistence.Column; > import javax.persistence.Entity; > import javax.persistence.FetchType; > import javax.persistence.GeneratedValue; > import javax.persistence.GenerationType; > import javax.persistence.Id; > import javax.persistence.ManyToOne; > import javax.persistence.Version; > > import org.apache.openjpa.persistence.jdbc.Strategy; > > @Entity > public class Pebble implements PersistentlyIdentifiable { > > @Id > @GeneratedValue(strategy = GenerationType.AUTO) > @Column(name = "id") > private Long assignedId; > > @Version > @Column(name = "version") > private long assignedVersion; > > @ManyToOne(cascade = { CascadeType.MERGE, CascadeType.REFRESH, > CascadeType.PERSIST }, fetch = FetchType.LAZY) > @Strategy("example.openjpa.PersistentInterfaceValueHandler") > protected BuildingMaterial buildingMaterial; > > public Long getId() { > return assignedId; > } > > public long getVersion() { > return assignedVersion; > } > > public void setBuildingMaterial(BuildingMaterial buildingMaterial) { > buildingMaterial.add(this); > } > > public Discriminator getDiscriminator() { > return Discriminator.PEBBLE; > } > > public BuildingMaterial getBuildingMaterial() { > return buildingMaterial; > } > } > ============================ > package example.model; > > public interface PersistentlyIdentifiable { > Comparable<?> getId(); > > long getVersion(); > > Discriminator getDiscriminator(); > } > ============================ > package example.model; > > import java.util.Set; > > public interface BuildingMaterial extends PersistentlyIdentifiable { > > void add(Pebble pebble); > > Set<Pebble> getPebbles(); > } > ============================ > package example.model; > > import java.util.HashMap; > import java.util.Map; > > /** > * Enum used to store discriminators in the database for polymorphic > references. > */ > public enum Discriminator { > > CONCRETE(Concrete.class), PEBBLE(Pebble.class); > > private static class X { > public static final Map<String, Discriminator> > discriminatorsByClassName = new HashMap<String, Discriminator>(); > > public static void storeByClassName(Discriminator > discriminator) { > synchronized (discriminatorsByClassName) { > > discriminatorsByClassName.put(discriminator.getClassName(), > discriminator); > } > } > } > > private Discriminator(Class<?> clazz) { > this.className = clazz.getName(); > X.storeByClassName(this); > } > > private String className; > > public String getClassName() { > return className; > } > > public static Discriminator getDiscriminatorEnum(String className) { > return X.discriminatorsByClassName.get(className); > } > > public static String getDiscriminatorName(Class<?> clazz) { > Discriminator d = getDiscriminatorEnum(clazz.getName()); > if (d == null) { > throw new IllegalArgumentException("Class " + > clazz.getName() > + " does not have a corresponding > Discriminator"); > } > return d.name(); > } > > public static Class<?> getDiscriminatorClassFor(String name) > throws ClassNotFoundException { > > return getDiscriminatorClassFor(name, Discriminator.class > .getClassLoader()); > } > > public static Class<?> getDiscriminatorClassFor(String name, > ClassLoader loader) throws ClassNotFoundException { > > return > Class.forName(Discriminator.valueOf(name).getClassName(), true, > loader); > } > } > =========================== > package example.openjpa; > > import java.sql.SQLException; > > import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration; > import org.apache.openjpa.jdbc.kernel.JDBCStore; > import org.apache.openjpa.jdbc.meta.ValueMapping; > import org.apache.openjpa.jdbc.meta.strats.AbstractValueHandler; > import org.apache.openjpa.jdbc.schema.Column; > import org.apache.openjpa.jdbc.schema.ColumnIO; > import org.apache.openjpa.kernel.OpenJPAStateManager; > import org.apache.openjpa.kernel.StoreContext; > import org.apache.openjpa.meta.JavaTypes; > import org.apache.openjpa.util.StoreException; > import org.slf4j.Logger; > import org.slf4j.LoggerFactory; > > import example.model.Discriminator; > import example.model.PersistentlyIdentifiable; > > @SuppressWarnings("serial") > public class PersistentInterfaceValueHandler extends AbstractValueHandler { > > Logger log = > LoggerFactory.getLogger(PersistentInterfaceValueHandler.class); > > @Override > public Column[] map(ValueMapping valueMapping, String fieldName, > ColumnIO columnIo, boolean adapt) { > Column discriminatorColumn = new Column(); > discriminatorColumn.setName(fieldName.toUpperCase() + > "_DISCRIMINATOR"); > discriminatorColumn.setJavaType(JavaTypes.STRING); > > Column idColumn = new Column(); > idColumn.setName(fieldName.toUpperCase() + "_ID"); > idColumn.setJavaType(JavaTypes.LONG); > > return new Column[] { discriminatorColumn, idColumn }; > } > > public boolean isVersionable(ValueMapping vm) { > return true; > } > > public boolean objectValueRequiresLoad(ValueMapping vm) { > return true; > } > > public Object toDataStoreValue(ValueMapping vm, Object val, > JDBCStore store) { > if (val == null) { > return new Object[] { null, null }; > } > return new Object[] { deriveDiscriminator(val.getClass()), > ((PersistentlyIdentifiable) val).getId() }; > } > > public String deriveDiscriminator(Class<?> clazz) { > if (log.isDebugEnabled()) { > log.debug("Getting discriminator for class: [" + > clazz.getName() > + "]"); > } > String discriminator = > Discriminator.getDiscriminatorName(clazz); > if (log.isDebugEnabled()) { > log.debug("Discriminator for class: [" + > clazz.getName() + "]: [" > + discriminator + "]"); > } > return discriminator; > } > > public Class<?> deriveClass(String name, ClassLoader loader) > throws ClassNotFoundException { > if (log.isDebugEnabled()) { > log.debug("Getting class for discriminator: [" + > name + "]"); > } > Class<?> clazz = > Discriminator.getDiscriminatorClassFor(name, loader); > if (log.isDebugEnabled()) { > log.debug("Class for discriminator: [" + name + "]: > [" > + clazz.getName() + "]"); > } > return clazz; > } > > public Object toObjectValue(ValueMapping vm, Object val, > OpenJPAStateManager sm, JDBCStore store, > JDBCFetchConfiguration fetch) throws SQLException { > > if (val == null) { > return null; > } > Object[] values = (Object[]) val; > StoreContext ctx = store.getContext(); > ClassLoader loader = store.getConfiguration() > > .getClassResolverInstance().getClassLoader(vm.getType(), > ctx.getClassLoader()); > Class<?> cls = null; > String clsName = (String) values[0]; > Long oidStr = (Long) values[1]; > if (clsName == null) { > return null; > } > try { > cls = deriveClass(clsName, loader); > } catch (ClassNotFoundException cnfe) { > throw new StoreException(cnfe); > } > Object oid = ctx.newObjectId(cls, oidStr); > return store.find(oid, vm, fetch); > } > } > ======================== > package example.test.integration; > > import static org.junit.Assert.assertEquals; > import static org.junit.Assert.assertNotNull; > > import java.util.Set; > > import org.junit.Test; > import org.springframework.transaction.annotation.Transactional; > > import example.model.BuildingMaterial; > import example.model.Concrete; > import example.model.Pebble; > > public class ConcretePebbleTest extends AbstractJpaTest { > > @Test > @Transactional > public void testConcrete() { > > BuildingMaterial bm = new Concrete(); > em.persist(bm); > > Pebble p = new Pebble(); > bm.add(p); > > em.flush(); > > Object bmid = bm.getId(); > Object pid = p.getId(); > > em.clear(); > > // navigate from BuildingMaterials side > bm = em.find(bm.getClass(), bmid); > Set<Pebble> pebbles = null; > assertNotNull(pebbles = bm.getPebbles()); > assertEquals(1, pebbles.size()); > > em.clear(); > > // navigate from Pebble side > p = em.find(Pebble.class, pid); > assertNotNull(bm = p.getBuildingMaterial()); > } > } > ======================= > package example.test.integration; > > import javax.annotation.PostConstruct; > import javax.persistence.EntityManager; > import javax.persistence.EntityManagerFactory; > import javax.persistence.PersistenceContext; > > import org.junit.runner.RunWith; > import org.springframework.beans.factory.annotation.Autowired; > import org.springframework.test.context.ContextConfiguration; > import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; > > @ContextConfiguration(locations={"classpath:applicationContext.xml"}) > @RunWith(SpringJUnit4ClassRunner.class) > abstract public class AbstractJpaTest { > > @Autowired > protected EntityManagerFactory emf; > > @PersistenceContext > protected EntityManager em; > > @PostConstruct > public void afterPropertiesSet() throws Exception { > emf.createEntityManager(); > } > } > ======================== > package example.test.unit; > > import static org.junit.Assert.assertEquals; > > import org.junit.Test; > > import example.model.Concrete; > import example.model.Discriminator; > > public class DiscriminatorTest { > > @Test > public void testDiscriminator() throws ClassNotFoundException { > Discriminator d = Discriminator.CONCRETE; > > assertEquals(Discriminator.getDiscriminatorName(Concrete.class), d > .name()); > > assertEquals(Discriminator.getDiscriminatorClassFor(d.name > ()), > Concrete.class); > } > } > =========================== > # log4j.properties > log4j.rootLogger=ERROR, A1 > log4j.appender.A1=org.apache.log4j.ConsoleAppender > log4j.appender.A1.layout=org.apache.log4j.PatternLayout > log4j.appender.A1.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p > %c %x - %m%n > > log4j.logger.example=debug > =========================== > # test.properties > test.database=my-integration-test > =========================== > <!-- applicationContext.xml --> > <?xml version="1.0" > encoding="UTF-8"?> > > <beans xmlns="http://www.springframework.org/schema/beans" > xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" > xmlns:aop="http://www.springframework.org/schema/aop" > xmlns:tx="http://www.springframework.org/schema/tx" > xmlns:context="http://www.springframework.org/schema/context" > xsi:schemaLocation="http://www.springframework.org/schema/beans > http://www.springframework.org/schema/beans/spring-beans-2.5.xsd > http://www.springframework.org/schema/aop > http://www.springframework.org/schema/aop/spring-aop-2.5.xsd > http://www.springframework.org/schema/tx > http://www.springframework.org/schema/tx/spring-tx-2.5.xsd > > http://www.springframework.org/schema/context > http://www.springframework.org/schema/context/spring-context-2.5.xsd"> > > <bean > > class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> > <property name="location" value="classpath:test.properties" > /> > </bean> > > <bean id="HsqldbDataSource" class="org.hsqldb.jdbc.jdbcDataSource"> > <property name="user" value="sa" /> > <property name="database" > value="jdbc:hsqldb:mem:${test.database}" /> > </bean> > > <bean id="entityManagerFactory" > > class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> > <property name="persistenceUnitName" value="case-sandbox" /> > <property name="jpaProperties"> > <value> > openjpa.AutoDetach=commit > openjpa.DetachState=fgs > openjpa.QueryCache=false > openjpa.jdbc.SubclassFetchMode=none > openjpa.jdbc.EagerFetchMode=none > > openjpa.jdbc.SynchronizeMappings=buildSchema(ForeignKeys=true) > </value> > </property> > <property name="jpaVendorAdapter"> > <bean > class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter"> > <property name="showSql" > value="${test.showSql}" /> > <property name="generateDdl" value="false" > /> > </bean> > </property> > <property name="dataSource" ref="HsqldbDataSource" /> > </bean> > <bean > > class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" > /> > > <bean id="transactionManager" > class="org.springframework.orm.jpa.JpaTransactionManager"> > <property name="entityManagerFactory" > ref="entityManagerFactory" /> > <property name="jpaDialect"> > <bean > class="org.springframework.orm.jpa.vendor.OpenJpaDialect" /> > </property> > <property name="dataSource" ref="HsqldbDataSource" /> > </bean> > <tx:annotation-driven transaction-manager="transactionManager" > proxy-target-class="true" /> > > </beans> > ============================= > <!-- orm.xml --> > <?xml version="1.0" encoding="UTF-8" ?> > <entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" > xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" > xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm > http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" > version="1.0"> > > <persistence-unit-metadata> > <persistence-unit-defaults> > <access>FIELD</access> > <cascade-persist/> > </persistence-unit-defaults> > </persistence-unit-metadata> > > </entity-mappings> > ================================ > <!-- persistence.xml --> > <?xml version="1.0" > encoding="UTF-8"?> > > <persistence xmlns="http://java.sun.com/xml/ns/persistence" > version="1.0"> > <persistence-unit name="case-sandbox"> > <provider> > org.apache.openjpa.persistence.PersistenceProviderImpl > </provider> > </persistence-unit> > </persistence> > =============================== > <!-- pom.xml --> > <?xml version="1.0" > encoding="UTF-8"?> > > <project xmlns="http://maven.apache.org/POM/4.0.0" > xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" > xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 > http://maven.apache.org/maven-v4_0_0.xsd"> > <modelVersion>4.0.0</modelVersion> > <groupId>org.mytest</groupId> > <artifactId>case-sandbox</artifactId> > <packaging>jar</packaging> > <version>1.0.0-SNAPSHOT</version> > <name>Sandbox</name> > <properties> > <surefire.debug.suspend>n</surefire.debug.suspend> > <test.showSql>true</test.showSql> > <spring.version>2.5.6</spring.version> > <slf4j.version>1.5.6</slf4j.version> > <jpa.spec.version>1.0</jpa.spec.version> > <openjpa.version>1.2.1</openjpa.version> > <hsql.version>1.8.0.1</hsql.version> > </properties> > <dependencies> > <dependency> > <groupId>org.slf4j</groupId> > <artifactId>slf4j-log4j12</artifactId> > <version>${slf4j.version}</version> > </dependency> > <dependency> > <groupId>org.slf4j</groupId> > <artifactId>slf4j-api</artifactId> > <version>${slf4j.version}</version> > </dependency> > <dependency> > <groupId>javax.persistence</groupId> > <artifactId>persistence-api</artifactId> > <version>${jpa.spec.version}</version> > </dependency> > <dependency> > <groupId>org.apache.openjpa</groupId> > <artifactId>openjpa</artifactId> > <version>${openjpa.version}</version> > </dependency> > <dependency> > <groupId>hsqldb</groupId> > <artifactId>hsqldb</artifactId> > <version>${hsql.version}</version> > </dependency> > <dependency> > <groupId>org.springframework</groupId> > <artifactId>spring</artifactId> > <version>${spring.version}</version> > </dependency> > <dependency> > <groupId>junit</groupId> > <artifactId>junit</artifactId> > <version>4.6</version> > </dependency> > <dependency> > <groupId>org.springframework</groupId> > <artifactId>spring-test</artifactId> > <version>${spring.version}</version> > <exclusions> > <exclusion> > <groupId>junit</groupId> > <artifactId>junit</artifactId> > </exclusion> > </exclusions> > </dependency> > <dependency> > <groupId>cglib</groupId> > <artifactId>cglib-nodep</artifactId> > <version>2.1_3</version> > </dependency> > <!-- > <dependency> > <groupId>org.jmock</groupId> > <artifactId>jmock-junit4</artifactId> > <scope>test</scope> > </dependency> > --> > </dependencies> > <build> > <testResources> > <testResource> > <filtering>true</filtering> > <directory>src/test/resources</directory> > <includes> > <include>**/*</include> > </includes> > </testResource> > </testResources> > <plugins> > <plugin> > <groupId>org.apache.maven.plugins</groupId> > <artifactId>maven-jar-plugin</artifactId> > <version>2.2</version> > <executions> > <execution> > <goals> > <goal>test-jar</goal> > </goals> > </execution> > </executions> > </plugin> > <plugin> > <groupId>org.apache.maven.plugins</groupId> > > <artifactId>maven-compiler-plugin</artifactId> > <configuration> > <source>1.6</source> > <target>1.6</target> > </configuration> > </plugin> > <plugin> > <groupId>org.apache.maven.plugins</groupId> > <artifactId>maven-antrun-plugin</artifactId> > <version>1.1</version> > <executions> > <execution> > <id>1</id> > > <phase>process-classes</phase> > <configuration> > <tasks> > <path > id="cp"> > > <path refid="maven.compile.classpath" /> > </path> > > <pathconvert > pathsep=" " property="arguments" > > dirsep="."> > <map > from="${basedir}/target/classes/" to="" /> > <map > from="${basedir}\target\classes\" to="" /> > > <mapper> > > <chainedmapper> > > <globmapper from="*.class" to="*" /> > > </chainedmapper> > > </mapper> > > <path id="model.path"> > > <fileset dir="${basedir}/target/classes/"> > > <include name="**/*.class" /> > > <exclude name="**/annotations/**/*.class" /> > > </fileset> > > </path> > > </pathconvert> > <java > classname="org.apache.openjpa.enhance.PCEnhancer" > > classpathref="cp" dir="target/classes" fork="true"> > <arg > line="${arguments} -p persistence.xml#case-sandbox" /> > </java> > </tasks> > </configuration> > <goals> > <goal>run</goal> > </goals> > </execution> > <execution> > <id>2</id> > <configuration> > <tasks> > <path > id="cp"> > > <path refid="maven.compile.classpath" /> > </path> > > <java classname="org.apache.openjpa.jdbc.meta.MappingTool" > > classpathref="cp" dir="target/classes" fork="true"> > > > <arg line="-p persistence.xml#case-sandbox" /> > > <arg line="-connectionDriverName org.hsqldb.jdbc.jdbcDataSource" /> > > <arg line="-connectionURL jdbc:hsqldb:mem:dummy" /> > > <arg line="-schemaAction build" /> > > <arg line="-sql ../test.sql" /> > > </java> > > </tasks> > </configuration> > <phase>compile</phase> > <goals> > <goal>run</goal> > </goals> > </execution> > > </executions> > </plugin> > <plugin> > <groupId>org.apache.maven.plugins</groupId> > > <artifactId>maven-surefire-plugin</artifactId> > <version>2.3</version> > <configuration> > > <argLine>-Xrunjdwp:transport=dt_socket,address=9998,server=y,suspend=${surefire.debug.suspend} > -Xmx512m</argLine> > <includes> > > <include>**/*Test.java</include> > </includes> > <excludes> > > <exclude>**/integration/**</exclude> > </excludes> > </configuration> > <executions> > <execution> > <id>integration-tests</id> > > <phase>integration-test</phase> > <goals> > <goal>test</goal> > </goals> > <configuration> > <skip>false</skip> > <excludes> > > <exclude>**/unit/**</exclude> > </excludes> > <includes> > > <include>**/integration/**/*Test.java</include> > </includes> > </configuration> > </execution> > </executions> > </plugin> > </plugins> > </build> > </project> > ================================ > |
||||||||||||||||
|
Matthew Adams
|
Hi Mike,
<response-to-disclosure>The sample (https://issues.apache.org/jira/secure/attachment/12423038/case-sandbox.zip) can be unzipped and run immediately via maven (as well as imported into eclipse). Try it--it'll be fun! :) </response-to-disclosure> I tried your suggestion of going to property access (for both Concrete & Pebble), which is very much **not** desirable IMHO, and moving the @OneToMany annotation to BuildingMaterial's getPebbles() method. I got an interesting result. The schema came out correctly with only two tables, but I got a runtime error attempting to persist a new Concrete: <openjpa-1.2.1-r752877:753278 nonfatal store error> org.apache.openjpa.persistence.EntityExistsException: Attempt to persist detached object "example.model.Concrete@81fb". If this is a new instance, make sure any version and/or auto-generated primary key fields are null/default when persisting. FailedObject: example.model.Concrete@81fb I've uploaded the property example to OPENJPA-1361 (https://issues.apache.org/jira/secure/attachment/12423454/case-sandbox-property-access.zip), but property access is not something I want to move to. Short of defining two methods for each property (one to be called by OpenJPA, one to be called by clients of the entity), I can't execute any business logic in the setter methods. That's the traditional argument against property access, one to which I subscribe. Thanks for thinking about the problem, though! -matthew On Tue, Oct 27, 2009 at 7:04 PM, Michael Dick <[hidden email]> wrote: > Hi Matthew, > > <disclosure>I haven't tried the example you submitted. </disclosure> > > 1. If I had to guess I'd say that it's because the interface > (BuildingMaterial) doesn't have the @OneToMany annotation, and as a result > it's not a valid target for the relationship. Adding the annotatation (and > switching to property access instead of field access) might help here. > > If you can't move to property access and add the annotation then I'm not > sure how you could use an interface.. Using a MappedSuperclass for > BuildingMaterial instead of an interface (could be an abstract > MappedSuperclass though). > > 2. I'd have to look into @JoinColumn, I suspect it would result in similar > behavior though (I'm prepared to be wrong here - haven't used JoinColumn > much). > > Hope this helps > -mike > > On Fri, Oct 23, 2009 at 12:54 PM, Matthew Adams <[hidden email]>wrote: > >> Hi all, >> >> NB: All source for this example is attached and is pasted at the end >> of this email, in case the attachment doesn't survive the mailing >> list. I've also entered the following JIRA to track this issue: >> https://issues.apache.org/jira/browse/OPENJPA-1361 >> >> Synopsis: bidirectional one-to-many relationship where the one side >> references the objects on the many side using a concrete class >> reference and the many side references the object on the one side >> using an interface reference. When specifying the mappedBy property >> of the @OneToMany annotation, OpenJPA throws the error: >> >> Collection field "example.model.Concrete.pebbles" declares that it is >> mapped by "example.model.Pebble.buildingMaterial", but this is not a >> valid inverse relation. >> >> When I remove the mappedBy property, everything works fine, except >> that OpenJPA creates an extra join table (Concrete_Pebble) where it >> shouldn't, IMHO. I have two questions, an answer to either one of >> which will suffice: >> >> 1. Why can't I use mappedBy in this scenario? >> 2. How do I annotate things with @JoinColumn or similar that will >> allow me to not have a join table and only the reference from Pebble >> back to its BuildingMaterial? >> >> Thanks, >> Matthew >> ==================== >> package example.model; >> >> import java.util.HashSet; >> import java.util.Set; >> >> import javax.persistence.CascadeType; >> import javax.persistence.Column; >> import javax.persistence.Entity; >> import javax.persistence.FetchType; >> import javax.persistence.GeneratedValue; >> import javax.persistence.GenerationType; >> import javax.persistence.Id; >> import javax.persistence.OneToMany; >> import javax.persistence.Version; >> >> @Entity >> public class Concrete implements BuildingMaterial { >> >> @Id >> @GeneratedValue(strategy = GenerationType.AUTO) >> @Column(name = "id") >> private Long assignedId; >> >> @Version >> @Column(name = "version") >> private long assignedVersion; >> >> @OneToMany(cascade = { CascadeType.MERGE, CascadeType.REFRESH, >> CascadeType.PERSIST }, fetch = FetchType.LAZY) >> public Set<Pebble> pebbles = new HashSet<Pebble>(); >> >> public Long getId() { >> return assignedId; >> } >> >> public long getVersion() { >> return assignedVersion; >> } >> >> public Discriminator getDiscriminator() { >> return Discriminator.CONCRETE; >> } >> >> public void add(Pebble pebble) { >> this.pebbles.add(pebble); >> pebble.buildingMaterial = this; >> } >> >> public Set<Pebble> getPebbles() { >> return new HashSet<Pebble>(pebbles); >> } >> } >> =========================== >> package example.model; >> >> import javax.persistence.CascadeType; >> import javax.persistence.Column; >> import javax.persistence.Entity; >> import javax.persistence.FetchType; >> import javax.persistence.GeneratedValue; >> import javax.persistence.GenerationType; >> import javax.persistence.Id; >> import javax.persistence.ManyToOne; >> import javax.persistence.Version; >> >> import org.apache.openjpa.persistence.jdbc.Strategy; >> >> @Entity >> public class Pebble implements PersistentlyIdentifiable { >> >> @Id >> @GeneratedValue(strategy = GenerationType.AUTO) >> @Column(name = "id") >> private Long assignedId; >> >> @Version >> @Column(name = "version") >> private long assignedVersion; >> >> @ManyToOne(cascade = { CascadeType.MERGE, CascadeType.REFRESH, >> CascadeType.PERSIST }, fetch = FetchType.LAZY) >> @Strategy("example.openjpa.PersistentInterfaceValueHandler") >> protected BuildingMaterial buildingMaterial; >> >> public Long getId() { >> return assignedId; >> } >> >> public long getVersion() { >> return assignedVersion; >> } >> >> public void setBuildingMaterial(BuildingMaterial buildingMaterial) { >> buildingMaterial.add(this); >> } >> >> public Discriminator getDiscriminator() { >> return Discriminator.PEBBLE; >> } >> >> public BuildingMaterial getBuildingMaterial() { >> return buildingMaterial; >> } >> } >> ============================ >> package example.model; >> >> public interface PersistentlyIdentifiable { >> Comparable<?> getId(); >> >> long getVersion(); >> >> Discriminator getDiscriminator(); >> } >> ============================ >> package example.model; >> >> import java.util.Set; >> >> public interface BuildingMaterial extends PersistentlyIdentifiable { >> >> void add(Pebble pebble); >> >> Set<Pebble> getPebbles(); >> } >> ============================ >> package example.model; >> >> import java.util.HashMap; >> import java.util.Map; >> >> /** >> * Enum used to store discriminators in the database for polymorphic >> references. >> */ >> public enum Discriminator { >> >> CONCRETE(Concrete.class), PEBBLE(Pebble.class); >> >> private static class X { >> public static final Map<String, Discriminator> >> discriminatorsByClassName = new HashMap<String, Discriminator>(); >> >> public static void storeByClassName(Discriminator >> discriminator) { >> synchronized (discriminatorsByClassName) { >> >> discriminatorsByClassName.put(discriminator.getClassName(), >> discriminator); >> } >> } >> } >> >> private Discriminator(Class<?> clazz) { >> this.className = clazz.getName(); >> X.storeByClassName(this); >> } >> >> private String className; >> >> public String getClassName() { >> return className; >> } >> >> public static Discriminator getDiscriminatorEnum(String className) { >> return X.discriminatorsByClassName.get(className); >> } >> >> public static String getDiscriminatorName(Class<?> clazz) { >> Discriminator d = getDiscriminatorEnum(clazz.getName()); >> if (d == null) { >> throw new IllegalArgumentException("Class " + >> clazz.getName() >> + " does not have a corresponding >> Discriminator"); >> } >> return d.name(); >> } >> >> public static Class<?> getDiscriminatorClassFor(String name) >> throws ClassNotFoundException { >> >> return getDiscriminatorClassFor(name, Discriminator.class >> .getClassLoader()); >> } >> >> public static Class<?> getDiscriminatorClassFor(String name, >> ClassLoader loader) throws ClassNotFoundException { >> >> return >> Class.forName(Discriminator.valueOf(name).getClassName(), true, >> loader); >> } >> } >> =========================== >> package example.openjpa; >> >> import java.sql.SQLException; >> >> import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration; >> import org.apache.openjpa.jdbc.kernel.JDBCStore; >> import org.apache.openjpa.jdbc.meta.ValueMapping; >> import org.apache.openjpa.jdbc.meta.strats.AbstractValueHandler; >> import org.apache.openjpa.jdbc.schema.Column; >> import org.apache.openjpa.jdbc.schema.ColumnIO; >> import org.apache.openjpa.kernel.OpenJPAStateManager; >> import org.apache.openjpa.kernel.StoreContext; >> import org.apache.openjpa.meta.JavaTypes; >> import org.apache.openjpa.util.StoreException; >> import org.slf4j.Logger; >> import org.slf4j.LoggerFactory; >> >> import example.model.Discriminator; >> import example.model.PersistentlyIdentifiable; >> >> @SuppressWarnings("serial") >> public class PersistentInterfaceValueHandler extends AbstractValueHandler { >> >> Logger log = >> LoggerFactory.getLogger(PersistentInterfaceValueHandler.class); >> >> @Override >> public Column[] map(ValueMapping valueMapping, String fieldName, >> ColumnIO columnIo, boolean adapt) { >> Column discriminatorColumn = new Column(); >> discriminatorColumn.setName(fieldName.toUpperCase() + >> "_DISCRIMINATOR"); >> discriminatorColumn.setJavaType(JavaTypes.STRING); >> >> Column idColumn = new Column(); >> idColumn.setName(fieldName.toUpperCase() + "_ID"); >> idColumn.setJavaType(JavaTypes.LONG); >> >> return new Column[] { discriminatorColumn, idColumn }; >> } >> >> public boolean isVersionable(ValueMapping vm) { >> return true; >> } >> >> public boolean objectValueRequiresLoad(ValueMapping vm) { >> return true; >> } >> >> public Object toDataStoreValue(ValueMapping vm, Object val, >> JDBCStore store) { >> if (val == null) { >> return new Object[] { null, null }; >> } >> return new Object[] { deriveDiscriminator(val.getClass()), >> ((PersistentlyIdentifiable) val).getId() }; >> } >> >> public String deriveDiscriminator(Class<?> clazz) { >> if (log.isDebugEnabled()) { >> log.debug("Getting discriminator for class: [" + >> clazz.getName() >> + "]"); >> } >> String discriminator = >> Discriminator.getDiscriminatorName(clazz); >> if (log.isDebugEnabled()) { >> log.debug("Discriminator for class: [" + >> clazz.getName() + "]: [" >> + discriminator + "]"); >> } >> return discriminator; >> } >> >> public Class<?> deriveClass(String name, ClassLoader loader) >> throws ClassNotFoundException { >> if (log.isDebugEnabled()) { >> log.debug("Getting class for discriminator: [" + >> name + "]"); >> } >> Class<?> clazz = >> Discriminator.getDiscriminatorClassFor(name, loader); >> if (log.isDebugEnabled()) { >> log.debug("Class for discriminator: [" + name + "]: >> [" >> + clazz.getName() + "]"); >> } >> return clazz; >> } >> >> public Object toObjectValue(ValueMapping vm, Object val, >> OpenJPAStateManager sm, JDBCStore store, >> JDBCFetchConfiguration fetch) throws SQLException { >> >> if (val == null) { >> return null; >> } >> Object[] values = (Object[]) val; >> StoreContext ctx = store.getContext(); >> ClassLoader loader = store.getConfiguration() >> >> .getClassResolverInstance().getClassLoader(vm.getType(), >> ctx.getClassLoader()); >> Class<?> cls = null; >> String clsName = (String) values[0]; >> Long oidStr = (Long) values[1]; >> if (clsName == null) { >> return null; >> } >> try { >> cls = deriveClass(clsName, loader); >> } catch (ClassNotFoundException cnfe) { >> throw new StoreException(cnfe); >> } >> Object oid = ctx.newObjectId(cls, oidStr); >> return store.find(oid, vm, fetch); >> } >> } >> ======================== >> package example.test.integration; >> >> import static org.junit.Assert.assertEquals; >> import static org.junit.Assert.assertNotNull; >> >> import java.util.Set; >> >> import org.junit.Test; >> import org.springframework.transaction.annotation.Transactional; >> >> import example.model.BuildingMaterial; >> import example.model.Concrete; >> import example.model.Pebble; >> >> public class ConcretePebbleTest extends AbstractJpaTest { >> >> @Test >> @Transactional >> public void testConcrete() { >> >> BuildingMaterial bm = new Concrete(); >> em.persist(bm); >> >> Pebble p = new Pebble(); >> bm.add(p); >> >> em.flush(); >> >> Object bmid = bm.getId(); >> Object pid = p.getId(); >> >> em.clear(); >> >> // navigate from BuildingMaterials side >> bm = em.find(bm.getClass(), bmid); >> Set<Pebble> pebbles = null; >> assertNotNull(pebbles = bm.getPebbles()); >> assertEquals(1, pebbles.size()); >> >> em.clear(); >> >> // navigate from Pebble side >> p = em.find(Pebble.class, pid); >> assertNotNull(bm = p.getBuildingMaterial()); >> } >> } >> ======================= >> package example.test.integration; >> >> import javax.annotation.PostConstruct; >> import javax.persistence.EntityManager; >> import javax.persistence.EntityManagerFactory; >> import javax.persistence.PersistenceContext; >> >> import org.junit.runner.RunWith; >> import org.springframework.beans.factory.annotation.Autowired; >> import org.springframework.test.context.ContextConfiguration; >> import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; >> >> @ContextConfiguration(locations={"classpath:applicationContext.xml"}) >> @RunWith(SpringJUnit4ClassRunner.class) >> abstract public class AbstractJpaTest { >> >> @Autowired >> protected EntityManagerFactory emf; >> >> @PersistenceContext >> protected EntityManager em; >> >> @PostConstruct >> public void afterPropertiesSet() throws Exception { >> emf.createEntityManager(); >> } >> } >> ======================== >> package example.test.unit; >> >> import static org.junit.Assert.assertEquals; >> >> import org.junit.Test; >> >> import example.model.Concrete; >> import example.model.Discriminator; >> >> public class DiscriminatorTest { >> >> @Test >> public void testDiscriminator() throws ClassNotFoundException { >> Discriminator d = Discriminator.CONCRETE; >> >> assertEquals(Discriminator.getDiscriminatorName(Concrete.class), d >> .name()); >> >> assertEquals(Discriminator.getDiscriminatorClassFor(d.name >> ()), >> Concrete.class); >> } >> } >> =========================== >> # log4j.properties >> log4j.rootLogger=ERROR, A1 >> log4j.appender.A1=org.apache.log4j.ConsoleAppender >> log4j.appender.A1.layout=org.apache.log4j.PatternLayout >> log4j.appender.A1.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p >> %c %x - %m%n >> >> log4j.logger.example=debug >> =========================== >> # test.properties >> test.database=my-integration-test >> =========================== >> <!-- applicationContext.xml --> >> <?xml version="1.0" >> encoding="UTF-8"?> >> >> <beans xmlns="http://www.springframework.org/schema/beans" >> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >> xmlns:aop="http://www.springframework.org/schema/aop" >> xmlns:tx="http://www.springframework.org/schema/tx" >> xmlns:context="http://www.springframework.org/schema/context" >> xsi:schemaLocation="http://www.springframework.org/schema/beans >> http://www.springframework.org/schema/beans/spring-beans-2.5.xsd >> http://www.springframework.org/schema/aop >> http://www.springframework.org/schema/aop/spring-aop-2.5.xsd >> http://www.springframework.org/schema/tx >> http://www.springframework.org/schema/tx/spring-tx-2.5.xsd >> >> http://www.springframework.org/schema/context >> http://www.springframework.org/schema/context/spring-context-2.5.xsd"> >> >> <bean >> >> class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> >> <property name="location" value="classpath:test.properties" >> /> >> </bean> >> >> <bean id="HsqldbDataSource" class="org.hsqldb.jdbc.jdbcDataSource"> >> <property name="user" value="sa" /> >> <property name="database" >> value="jdbc:hsqldb:mem:${test.database}" /> >> </bean> >> >> <bean id="entityManagerFactory" >> >> class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> >> <property name="persistenceUnitName" value="case-sandbox" /> >> <property name="jpaProperties"> >> <value> >> openjpa.AutoDetach=commit >> openjpa.DetachState=fgs >> openjpa.QueryCache=false >> openjpa.jdbc.SubclassFetchMode=none >> openjpa.jdbc.EagerFetchMode=none >> >> openjpa.jdbc.SynchronizeMappings=buildSchema(ForeignKeys=true) >> </value> >> </property> >> <property name="jpaVendorAdapter"> >> <bean >> class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter"> >> <property name="showSql" >> value="${test.showSql}" /> >> <property name="generateDdl" value="false" >> /> >> </bean> >> </property> >> <property name="dataSource" ref="HsqldbDataSource" /> >> </bean> >> <bean >> >> class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" >> /> >> >> <bean id="transactionManager" >> class="org.springframework.orm.jpa.JpaTransactionManager"> >> <property name="entityManagerFactory" >> ref="entityManagerFactory" /> >> <property name="jpaDialect"> >> <bean >> class="org.springframework.orm.jpa.vendor.OpenJpaDialect" /> >> </property> >> <property name="dataSource" ref="HsqldbDataSource" /> >> </bean> >> <tx:annotation-driven transaction-manager="transactionManager" >> proxy-target-class="true" /> >> >> </beans> >> ============================= >> <!-- orm.xml --> >> <?xml version="1.0" encoding="UTF-8" ?> >> <entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" >> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >> xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm >> http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" >> version="1.0"> >> >> <persistence-unit-metadata> >> <persistence-unit-defaults> >> <access>FIELD</access> >> <cascade-persist/> >> </persistence-unit-defaults> >> </persistence-unit-metadata> >> >> </entity-mappings> >> ================================ >> <!-- persistence.xml --> >> <?xml version="1.0" >> encoding="UTF-8"?> >> >> <persistence xmlns="http://java.sun.com/xml/ns/persistence" >> version="1.0"> >> <persistence-unit name="case-sandbox"> >> <provider> >> org.apache.openjpa.persistence.PersistenceProviderImpl >> </provider> >> </persistence-unit> >> </persistence> >> =============================== >> <!-- pom.xml --> >> <?xml version="1.0" >> encoding="UTF-8"?> >> >> <project xmlns="http://maven.apache.org/POM/4.0.0" >> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 >> http://maven.apache.org/maven-v4_0_0.xsd"> >> <modelVersion>4.0.0</modelVersion> >> <groupId>org.mytest</groupId> >> <artifactId>case-sandbox</artifactId> >> <packaging>jar</packaging> >> <version>1.0.0-SNAPSHOT</version> >> <name>Sandbox</name> >> <properties> >> <surefire.debug.suspend>n</surefire.debug.suspend> >> <test.showSql>true</test.showSql> >> <spring.version>2.5.6</spring.version> >> <slf4j.version>1.5.6</slf4j.version> >> <jpa.spec.version>1.0</jpa.spec.version> >> <openjpa.version>1.2.1</openjpa.version> >> <hsql.version>1.8.0.1</hsql.version> >> </properties> >> <dependencies> >> <dependency> >> <groupId>org.slf4j</groupId> >> <artifactId>slf4j-log4j12</artifactId> >> <version>${slf4j.version}</version> >> </dependency> >> <dependency> >> <groupId>org.slf4j</groupId> >> <artifactId>slf4j-api</artifactId> >> <version>${slf4j.version}</version> >> </dependency> >> <dependency> >> <groupId>javax.persistence</groupId> >> <artifactId>persistence-api</artifactId> >> <version>${jpa.spec.version}</version> >> </dependency> >> <dependency> >> <groupId>org.apache.openjpa</groupId> >> <artifactId>openjpa</artifactId> >> <version>${openjpa.version}</version> >> </dependency> >> <dependency> >> <groupId>hsqldb</groupId> >> <artifactId>hsqldb</artifactId> >> <version>${hsql.version}</version> >> </dependency> >> <dependency> >> <groupId>org.springframework</groupId> >> <artifactId>spring</artifactId> >> <version>${spring.version}</version> >> </dependency> >> <dependency> >> <groupId>junit</groupId> >> <artifactId>junit</artifactId> >> <version>4.6</version> >> </dependency> >> <dependency> >> <groupId>org.springframework</groupId> >> <artifactId>spring-test</artifactId> >> <version>${spring.version}</version> >> <exclusions> >> <exclusion> >> <groupId>junit</groupId> >> <artifactId>junit</artifactId> >> </exclusion> >> </exclusions> >> </dependency> >> <dependency> >> <groupId>cglib</groupId> >> <artifactId>cglib-nodep</artifactId> >> <version>2.1_3</version> >> </dependency> >> <!-- >> <dependency> >> <groupId>org.jmock</groupId> >> <artifactId>jmock-junit4</artifactId> >> <scope>test</scope> >> </dependency> >> --> >> </dependencies> >> <build> >> <testResources> >> <testResource> >> <filtering>true</filtering> >> <directory>src/test/resources</directory> >> <includes> >> <include>**/*</include> >> </includes> >> </testResource> >> </testResources> >> <plugins> >> <plugin> >> <groupId>org.apache.maven.plugins</groupId> >> <artifactId>maven-jar-plugin</artifactId> >> <version>2.2</version> >> <executions> >> <execution> >> <goals> >> <goal>test-jar</goal> >> </goals> >> </execution> >> </executions> >> </plugin> >> <plugin> >> <groupId>org.apache.maven.plugins</groupId> >> >> <artifactId>maven-compiler-plugin</artifactId> >> <configuration> >> <source>1.6</source> >> <target>1.6</target> >> </configuration> >> </plugin> >> <plugin> >> <groupId>org.apache.maven.plugins</groupId> >> <artifactId>maven-antrun-plugin</artifactId> >> <version>1.1</version> >> <executions> >> <execution> >> <id>1</id> >> >> <phase>process-classes</phase> >> <configuration> >> <tasks> >> <path >> id="cp"> >> >> <path refid="maven.compile.classpath" /> >> </path> >> >> <pathconvert >> pathsep=" " property="arguments" >> >> dirsep="."> >> <map >> from="${basedir}/target/classes/" to="" /> >> <map >> from="${basedir}\target\classes\" to="" /> >> >> <mapper> >> >> <chainedmapper> >> >> <globmapper from="*.class" to="*" /> >> >> </chainedmapper> >> >> </mapper> >> >> <path id="model.path"> >> >> <fileset dir="${basedir}/target/classes/"> >> >> <include name="**/*.class" /> >> >> <exclude name="**/annotations/**/*.class" /> >> >> </fileset> >> >> </path> >> >> </pathconvert> >> <java >> classname="org.apache.openjpa.enhance.PCEnhancer" >> >> classpathref="cp" dir="target/classes" fork="true"> >> <arg >> line="${arguments} -p persistence.xml#case-sandbox" /> >> </java> >> </tasks> >> </configuration> >> <goals> >> <goal>run</goal> >> </goals> >> </execution> >> <execution> >> <id>2</id> >> <configuration> >> <tasks> >> <path >> id="cp"> >> >> <path refid="maven.compile.classpath" /> >> </path> >> >> <java classname="org.apache.openjpa.jdbc.meta.MappingTool" >> >> classpathref="cp" dir="target/classes" fork="true"> >> >> >> <arg line="-p persistence.xml#case-sandbox" /> >> >> <arg line="-connectionDriverName org.hsqldb.jdbc.jdbcDataSource" /> >> >> <arg line="-connectionURL jdbc:hsqldb:mem:dummy" /> >> >> <arg line="-schemaAction build" /> >> >> <arg line="-sql ../test.sql" /> >> >> </java> >> >> </tasks> >> </configuration> >> <phase>compile</phase> >> <goals> >> <goal>run</goal> >> </goals> >> </execution> >> >> </executions> >> </plugin> >> <plugin> >> <groupId>org.apache.maven.plugins</groupId> >> >> <artifactId>maven-surefire-plugin</artifactId> >> <version>2.3</version> >> <configuration> >> >> <argLine>-Xrunjdwp:transport=dt_socket,address=9998,server=y,suspend=${surefire.debug.suspend} >> -Xmx512m</argLine> >> <includes> >> >> <include>**/*Test.java</include> >> </includes> >> <excludes> >> >> <exclude>**/integration/**</exclude> >> </excludes> >> </configuration> >> <executions> >> <execution> >> <id>integration-tests</id> >> >> <phase>integration-test</phase> >> <goals> >> <goal>test</goal> >> </goals> >> <configuration> >> <skip>false</skip> >> <excludes> >> >> <exclude>**/unit/**</exclude> >> </excludes> >> <includes> >> >> <include>**/integration/**/*Test.java</include> >> </includes> >> </configuration> >> </execution> >> </executions> >> </plugin> >> </plugins> >> </build> >> </project> >> ================================ >> > -- mailto:[hidden email] skype:matthewadams12 yahoo:matthewadams aol:matthewadams12 google-talk:[hidden email] msn:[hidden email] http://matthewadams.me http://www.linkedin.com/in/matthewadams |
||||||||||||||||
| Free Embeddable Forum Powered by Nabble | Help |