Strange behaviour for unidirectional 1-M associations and Association Overrides

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

Strange behaviour for unidirectional 1-M associations and Association Overrides

Wolfgang Winter
Sorry, this is a somewhat lengthy issue but I had several problems with unidirectional 1-M associations and Association Overrides. Eventually, I could find a workaround
but it looks to me as if there are some bugs and misfits to the JPA specification in OpenJPA. Here is the story:
 
I get following exception using Tomee 1.6:

<openjpa-2.3.0-nonfinal-1540826-r422266:1542644 fatal user error> org.apache.openjpa.persistence.ArgumentException: You have supplied columns for "com.logitags.cibet.actuator.archive.Archive.resource.com.logitags.cibet.core.Resource.parameters", 
but this mapping cannot have columns in this context.
	at org.apache.openjpa.jdbc.meta.MappingInfo.assertNoSchemaComponents(MappingInfo.java:382)
	at org.apache.openjpa.jdbc.meta.strats.RelationToManyTableFieldStrategy.map(RelationToManyTableFieldStrategy.java:97)
	at org.apache.openjpa.jdbc.meta.strats.RelationCollectionTableFieldStrategy.map(RelationCollectionTableFieldStrategy.java:94)
	at org.apache.openjpa.jdbc.meta.FieldMapping.setStrategy(FieldMapping.java:146)
	at org.apache.openjpa.jdbc.meta.RuntimeStrategyInstaller.installStrategy(RuntimeStrategyInstaller.java:82)

with following classes and persistence.xml

@Entity
public class Archive implements Serializable { 
  @Id 
  @GeneratedValue(strategy = GenerationType.TABLE, generator = "CIB_SEQUENCE") 
  @TableGenerator(name = "CIB_SEQUENCE", table = "CIB_SEQUENCE", pkColumnName = "SEQUENCE", valueColumnName = "HI", pkColumnValue = "Archive", allocationSize = 1) 
  private long archiveId; 
  
  @Embedded 
  @AssociationOverride(name = "parameters", joinColumns = @JoinColumn(name = "archiveId", referencedColumnName = "archiveId")) 
  private Resource resource; 
}

@Entity
public class DcControllable implements Serializable { 
  @Id 
  @GeneratedValue(strategy = GenerationType.AUTO) 
  private long dcControllableId; 
  
  @Embedded 
  @AssociationOverride(name = "parameters", joinColumns = @JoinColumn(name = "dcControllableId", referencedColumnName = "dcControllableId")) 
  private Resource resource; 
}

@Embeddable
public class Resource implements Serializable, Cloneable {

  @OneToMany(cascade ={ CascadeType.ALL }, fetch = FetchType.LAZY)
  @JoinColumn(name = "dummy")
  private List<ResourceParameter> parameters = new LinkedList<ResourceParameter>();
}


<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
	 version="2.0">

   <persistence-unit name="Cibet" transaction-type="JTA">
   	  <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
      <jta-data-source>java:/CibetDS</jta-data-source>
      <jar-file>../lib/cibet-${project.version}.jar</jar-file>
      <properties>
      	<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)" />
      </properties>
   </persistence-unit>
</persistence>

With annotations to use a join table it works. Join tables CIB_ARCHIVEPARAMETER and CIB_DCPARAMETER are created:

@Entity
public class Archive implements Serializable { 
  
  @Embedded 
  @AssociationOverride(name = "parameters", joinTable = @JoinTable(name = "CIB_ARCHIVEPARAMETER", joinColumns = @JoinColumn(name = "ARCHIVEID", referencedColumnName = "archiveId"), inverseJoinColumns = @JoinColumn(name = "PARAMETERID", referencedColumnName = "parameterId", unique = true)))
  private Resource resource; 
}

@Entity
public class DcControllable implements Serializable { 
  
  @Embedded 
  @AssociationOverride(name = "parameters", joinTable = @JoinTable(name = "CIB_DCPARAMETER", joinColumns = @JoinColumn(name = "DCCONTROLLABLEID", referencedColumnName = "dcControllableId"), inverseJoinColumns = @JoinColumn(name = "PARAMETERID", referencedColumnName = "parameterId", unique = true))) 
  private Resource resource; 
}

@Embeddable
public class Resource implements Serializable, Cloneable {

  @OneToMany(cascade ={ CascadeType.ALL }, fetch = FetchType.LAZY)
  @JoinTable
  private List<ResourceParameter> parameters = new LinkedList<ResourceParameter>();
}

However, the first solution using columns archiveId and
dcControllableId as foreign keys in ResourceParameter table without join table is working with Hibernate and EclipseLink, therefore I have to find a workaround for OpenJPA. The idea is to define the join table association in an orm.xml
that is only applied when using OpenJPA. As I want to replace the @JoinColumn with a @JoinTable in class Resource I have to use metadata-complete="true":

<?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_2_0.xsd"
	version="2.0">
	<description>Special mapping for OpenJPA because of bug in OpenJPA OneToMany with JoinColumn</description>
	
	<entity class="com.logitags.cibet.actuator.archive.Archive" name="Archive">       
		<attributes>
			<embedded name="resource">
				<association-override name="parameters">
					<join-table name="CIB_ARCHIVEPARAMETER">
						<join-column name="ARCHIVEID" referenced-column-name="archiveId"/>
						<inverse-join-column name="PARAMETERID" referenced-column-name="parameterId" unique="true"/>
					</join-table>
				</association-override>
			</embedded>
		</attributes>
	</entity>

	<entity class="com.logitags.cibet.actuator.dc.DcControllable" name="DcControllable">       
		<attributes>
			<embedded name="resource">
				<association-override name="parameters">
					<join-table name="CIB_DCPARAMETER">
						<join-column name="DCCONTROLLABLEID" referenced-column-name="dcControllableId"/>
						<inverse-join-column name="PARAMETERID" referenced-column-name="parameterId" unique="true"/>
					</join-table>
				</association-override>
			</embedded>
		</attributes>
	</entity>
	
	<embeddable class="com.logitags.cibet.core.Resource" metadata-complete="true">
		<attributes>
		  ...
			<one-to-many name="parameters" >
				<join-table />
				<cascade>
					<cascade-all/>
				</cascade>
			</one-to-many>
		</attributes>
	</embeddable>
	
</entity-mappings>	

This yields the same exception as above. It seems the association-overrides in orm.xml are ignored and the annotations are used again.
Therefore I use metadata-complete="true" also for Archive and DcControllable:

<?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_2_0.xsd"
	version="2.0">
	<description>Special mapping for OpenJPA because of bug in OpenJPA OneToMany with JoinColumn</description>
	
	<entity class="com.logitags.cibet.actuator.archive.Archive" name="Archive"  metadata-complete="true">  
		<table name="CIB_ARCHIVE"/> 
		<attributes>
		  ...
			<embedded name="resource">
				<association-override name="parameters">
					<join-table name="CIB_ARCHIVEPARAMETER">
						<join-column name="ARCHIVEID" referenced-column-name="archiveId" />
						<inverse-join-column name="PARAMETERID" referenced-column-name="parameterId" unique="true" />
					</join-table>
				</association-override>
			</embedded>
		</attributes>
	</entity>

	<entity class="com.logitags.cibet.actuator.dc.DcControllable" name="DcControllable" metadata-complete="true">       
		<table name="CIB_DCCONTROLLABLE"/> 
		<attributes>
		  ...
			<embedded name="resource">
				<association-override name="parameters">
					<join-table name="CIB_DCPARAMETER">
						<join-column name="DCCONTROLLABLEID" referenced-column-name="dcControllableId" />
						<inverse-join-column name="PARAMETERID" referenced-column-name="parameterId" unique="true" />
					</join-table>
				</association-override>
			</embedded>
		</attributes>
	</entity>
	
	<embeddable class="com.logitags.cibet.core.Resource" metadata-complete="true">
		  ... same as above ...
	</embeddable>
	
</entity-mappings>	

At the end this works but not as expected: Instead of creating the join tables, columns archiveId and
dcControllableId in ResourceParameter table are used as foreign keys. Very strange! Does OpenJPA make an internal optimization?