Extensible Entities with extensions of type other than string [message #880331] |
Fri, 01 June 2012 21:33 |
GianMaria Romanato Messages: 72 Registered: July 2009 |
Member |
|
|
Hello,
I am experimenting with EclipseLink support for Extensible Entities, as explained in this article: wiki.eclipse.org/EclipseLink/Examples/JPA/Extensibility
I first tried adding to an entity a virtual attribute of type string and providing the metadata via XML file. I also tried providing my custom implementation of MetadataSource and everything worked fine with the string attribute (altough my feeling is that API looks a bit unpolished because XMLEntityMappings is located and uses several classes in *internal* packages, Javadoc could be improved, and you need to initialize countless empty lists otherwise you'll experience NPE at runtime).
I then tried with a virtual attribute of type Integer, set it to 10, and it was saved properly in a VARCHAR flex column.
However, when I tried to query the entity table selecting the row where the flex column contains the integer with value equal to 10, I got a runtime error from the database (using Postgres) because the system cannot compare integers and strings:
ERROR: operator does not exist: character varying = integer
Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts.
According to this design document wiki.eclipse.org/EclipseLink/DesignDocs/340192 it should be possible to query entities based on the extensions, and I would hope that this applies not only to extensions that are of type String.
Is the above issue limited to Postgres or does it affect any database and is related to Eclipse Link? Or is it a problem with the query? select count(c) from Customer c where c.rating = 10, rating being the integer virtual field mapped to the varchar flex column
thanks
GianMaria.
|
|
|
|
|
|
|
Re: Extensible Entities with extensions of type other than string [message #891829 is a reply to message #891730] |
Tue, 26 June 2012 07:48 |
GianMaria Romanato Messages: 72 Registered: July 2009 |
Member |
|
|
Ciao Andriy,
My code would not be a good example: it's way more complex than needed because it's blended in a large system with many more additional features. I'll instead try to extract the most significant bits.
1) define your model object and make it extensible (mind the annotation @VirtualAccessMethods and the set() get() methods)
@Entity
@VirtualAccessMethods
public class Customer implements Serializable {
@Id
@GeneratedValue
private String id;
@Basic
private String name;
@Basic
private String lastName;
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getLastName() {
return lastName;
}
public Customer() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Transient
private Map<String, Object> extensions;
public <T> T get(String name) {
if (extensions == null) {
return null;
}
return (T) extensions.get(name);
}
public Object set(String name, Object value) {
if (extensions==null) {
extensions = new HashMap<String, Object>();
}
return extensions.put(name, value);
}
}
2) Additional metadata defined in a file. Define your persistence.xml so that it refers to the metadata file.
Important: in all the XML examples I had to remove "http" prefix from the schema URLS otherwise the forums will not let me post the message because I have not posted 25 messages yet(!)
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns:xsi="www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="java.sun.com/xml/ns/persistence java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0" xmlns="java.sun.com/xml/ns/persistence">
<persistence-unit name="customer" transaction-type="RESOURCE_LOCAL">
<class>model.Customer</class>
<properties>
<property name="eclipselink.metadata-source" value="XML"/>
<property name="eclipselink.metadata-source.xml.url" value="file:///home/giamma/eclipselink-orm.xml"/>
<property name="eclipselink.logging.level" value="FINEST"/>
<!-- your connection properties here -->
<property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver" />
<property name="javax.persistence.jdbc.url" value="jdbc:postgresql://127.0.0.1/customer" />
<property name="javax.persistence.jdbc.user" value="user" />
<property name="javax.persistence.jdbc.password" value="password" />
<!-- create tables on start -->
<property name="eclipselink.ddl-generation" value="create-tables" />
<property name="eclipselink.ddl-generation.output-mode"
value="database" />
<!-- Logging -->
<property name="eclipselink.logging.logger" value="DefaultLogger"/>
<property name="eclipselink.logging.level" value="FINEST" />
<property name="eclipselink.logging.timestamp" value="false" />
<property name="eclipselink.logging.session" value="true" />
<property name="eclipselink.logging.thread" value="false" />
</properties>
</persistence-unit>
</persistence>
Then define the eclipselink-orm.xml metadata file to declare the extra fields and
the mapping to database columns. My understanding is that you'll have to create
the column manually.
<entity-mappings
xmlns="www.eclipse.org/eclipselink/xsds/persistence/orm"
xmlns:xsi="www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="www.eclipse.org/eclipselink/xsds/persistence/orm www.eclipse.org/eclipselink/xsds/eclipselink_orm_2_1.xsd"
version="2.1">
<entity class="model.Customer">
<attributes>
<basic name="middleName" access="VIRTUAL" attribute-type="String">
<column name="FLEX1"/>
<access-methods get-method="get" set-method="set" />
</basic>
</attributes>
</entity>
</entity-mappings>
The above should be enough for the sample to work. If you want a per-tenant discriminator column make sure to pass the information when creating the EMF:
HashMap<String, Object> properties = new HashMap<String, Object>();
properties.put(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, tenantId);
properties.put(PersistenceUnitProperties.MULTITENANT_SHARED_EMF, Boolean.FALSE.toString());
properties.put(PersistenceUnitProperties.SESSION_NAME, unitName + "@" + tenantId);
EntityManagerFactory factory = Persistence.createEntityManagerFactory("customer", properties);
Note: I am developing in OSGi enterprise and I am not sure whether in JavaSE/J2EE you pass the properties to Persistence.createEntityManagerFactory() or to EntityManagerFactory.createEntityManager(). If the above does not work, try with the EMF.
3) If you want to programmatically contribute the metadata, you can either change the persistence.xml to declare a classname instead of an URL:
<property name="eclipselink.metadata-source" value="com.foo.MetadataRepository"/>
as explained here: wiki.eclipse.org/EclipseLink/UserGuide/JPA/Advanced_JPA_Development/Extensible_Entities
or you can pass the MetadataSource instance to EclipseLink in the same properties as above:
properties.put(PersistenceUnitProperties.METADATA_SOURCE, new MyMetadataSource());
Your class must implement org.eclipse.persistence.jpa.metadata.MetadataSource and it is responsible for creating an instance of XMLEntityMappings in method
public XMLEntityMappings getEntityMappings(Map<String, Object> properties, ClassLoader classLoader, SessionLog log)
Unluckily, you must initialise all the lists of the mapping objects you create, otherwise eclipse link will fail at runtime with a NullPointerException. This is a bit annoying.
For example, to programmatically re-create the metadata mapping of the XML above:
XMLEntityMappings mappings = new XMLEntityMappings();
// init lists
mappings.setEntities(new ArrayList());
mappings.setConverters(new ArrayList(0));
mappings.setEmbeddables(new ArrayList(0));
mappings.setMappedSuperclasses(new ArrayList(0));
mappings.setNamedNativeQueries(new ArrayList(0));
mappings.setNamedQueries(new ArrayList(0));
mappings.setNamedStoredFunctionQueries(new ArrayList(0));
mappings.setNamedStoredProcedureQueries(new ArrayList(0));
mappings.setNamedPLSQLStoredFunctionQueries(new ArrayList(0));
mappings.setNamedPLSQLStoredProcedureQueries(new ArrayList(0));
mappings.setObjectTypeConverters(new ArrayList(0));
mappings.setPLSQLRecords(new ArrayList(0));
mappings.setPLSQLTables(new ArrayList(0));
mappings.setSequenceGenerators(new ArrayList(0));
mappings.setSqlResultSetMappings(new ArrayList(0));
mappings.setStructConverters(new ArrayList(0));
mappings.setTableGenerators(new ArrayList(0));
mappings.setTypeConverters(new ArrayList(0));
mappings.setPartitioning(new ArrayList(0));
mappings.setRangePartitioning(new ArrayList(0));
mappings.setValuePartitioning(new ArrayList(0));
mappings.setUnionPartitioning(new ArrayList(0));
mappings.setReplicationPartitioning(new ArrayList(0));
mappings.setRoundRobinPartitioning(new ArrayList(0));
mappings.setHashPartitioning(new ArrayList(0));
mappings.setPinnedPartitioning(new ArrayList(0));
mappings.setTenantDiscriminatorColumns(new ArrayList(0));
EntityAccessor entity = new EntityAccessor();
mappings.getEntities().add(entity);
entity.setAssociationOverrides(new ArrayList(0));
entity.setAttributeOverrides(new ArrayList(0));
entity.setCacheIndexes(new ArrayList(0));
entity.setConverters(new ArrayList(0));
entity.setEntityListeners(new ArrayList(0));
entity.setFetchGroups(new ArrayList(0));
entity.setIndexes(new ArrayList(0));
entity.setNamedNativeQueries(new ArrayList(0));
entity.setNamedPLSQLStoredFunctionQueries(new ArrayList(0));
entity.setNamedQueries(new ArrayList(0));
entity.setNamedStoredFunctionQueries(new ArrayList(0));
entity.setNamedStoredProcedureQueries(new ArrayList(0));
entity.setObjectTypeConverters(new ArrayList(0));
entity.setPLSQLRecords(new ArrayList(0));
entity.setPLSQLTables(new ArrayList(0));
entity.setPrimaryKeyJoinColumns(new ArrayList(0));
entity.setProperties(new ArrayList(0));
entity.setSecondaryTables(new ArrayList(0));
entity.setSqlResultSetMappings(new ArrayList(0));
entity.setStructConverters(new ArrayList(0));
entity.setTypeConverters(new ArrayList(0));
// changed from setName() to setClassName() as pointed out by Thomas Gillet below
entity.setClassName("model.Customer");
XMLAttributes attributes = new XMLAttributes();
entity.setAttributes(attributes);
attributes.setArrays(new ArrayList(0));
attributes.setBasicCollections(new ArrayList(0));
attributes.setBasicMaps(new ArrayList(0));
attributes.setBasics(new ArrayList(0));
attributes.setElementCollections(new ArrayList(0));
attributes.setEmbeddeds(new ArrayList(0));
attributes.setIds(new ArrayList(0));
attributes.setManyToManys(new ArrayList(0));
attributes.setManyToOnes(new ArrayList(0));
attributes.setOneToManys(new ArrayList(0));
attributes.setOneToOnes(new ArrayList(0));
attributes.setStructures(new ArrayList(0));
attributes.setTransformations(new ArrayList(0));
attributes.setTransients(new ArrayList(0));
attributes.setVariableOneToOnes(new ArrayList(0));
attributes.setVersions(new ArrayList(0));
entity.setAttributes(EntityMappings.newAttributes());
BasicAccessor accessor = new BasicAccessor();
accessor.setAccess("VIRTUAL");
accessor.setConverters(new ArrayList(0));
accessor.setObjectTypeConverters(new ArrayList(0));
accessor.setProperties(new ArrayList(0));
accessor.setStructConverters(new ArrayList(0));
accessor.setTypeConverters(new ArrayList(0));
entity.getAttributes().getBasics().add(accessor);
accessor.setName("secondName");
ColumnMetadata cdata = new ColumnMetadata();
accessor.setColumn(cdata);
cdata.setName("FLEX1");
accessor.setAttributeType("String");
Ant that's it. The above may be inaccurate, as I did not compile anything, just copy/pasted and adapted some code. But you should be able to easily fix any issue.
ciao.
Giamma
EDIT: fixed the code as per Thomas' suggestion
[Updated on: Tue, 14 August 2012 08:54] Report message to a moderator
|
|
|
|
|
|
|
|
|
Re: Extensible Entities with extensions of type other than string [message #896389 is a reply to message #892581] |
Wed, 18 July 2012 09:02 |
GianMaria Romanato Messages: 72 Registered: July 2009 |
Member |
|
|
Hi Chris,
I am back vacation and I tried to follow your suggestion. I had mixed results, in the sense that it seems to work but only partially.
Here is what I did.
- I added a new column to my entity table named FLEX01 of type varchar(255)
- I added a new virtual attribute (age) to the extensible entity (Customer) and declared the attribute of type Integer via a metadata source as follows:
XMLEntityMappings mappings = new XMLEntityMappings();
// omitted initialization of all list attributes
EntityMappings entity = new EntityMappings();
// omitted initialization of all list attributes
entity.setName(Customer.class.getName());
entity.setClassName(Customer.class.getName());
mappings.getEntities().add(entity);
entity.setAttributes(EntityMappings.newAttributes()); // creates the attributes and initializes all the lists
BasicAccessor accessor = EntityMappings.newVirtualBasicAccessor(); // creates BasicAccessor and initializes all the lists
entity.getAttributes().getBasics().add(accessor);
accessor.setName("age");
ColumnMetadata cdata = new ColumnMetadata();
accessor.setColumn(cdata);
cdata.setName("FLEX01");
accessor.setAttributeType(Integer.class.getName());
TypeConverterMetadata tcmd = new TypeConverterMetadata();
tcmd.setName("IntegerToString");
MetadataClass objectType = new MetadataClass(null, Integer.class);
MetadataClass dataType = new MetadataClass(null, String.class);
tcmd.setObjectType(objectType);
tcmd.setDataType(dataType);
accessor.setTypeConverters(Collections.singletonList(tcmd));
Now, if a try to load the entity via a "select all" such as "select p from Customer p"
it works and all Customer objects are created and include in their extension attributes my Integer attribute.
On the other hand, if I am trying to select all persons where my extension attribute has a given integer value, for example "select p from Customer p where p.age = 33" I get the following error (testing with PostgreSQL 9.1)
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.4.0.v20120327-r11047): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.postgresql.util.PSQLException: ERROR: operator does not exist: character varying = integer
Suggerimento: No operator matches the given name and argument type(s). You might need to add explicit type casts.
Posizione: 95
Error Code: 0
Call: SELECT ID, TENANT_ID, FLEX01, FLEX02, NAME FROM CUSTOMER WHERE ((FLEX01 = ?) AND (TENANT_ID = ?))
bind => [333, 1]
Query: ReadAllQuery(referenceClass=Customer sql="SELECT ID, TENANT_ID, FLEX01, FLEX02, NAME FROM CUSTOMER WHERE ((FLEX01 = ?) AND (TENANT_ID = ?))")
at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:333)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:646)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeCall(DatabaseAccessor.java:537)
at org.eclipse.persistence.internal.sessions.AbstractSession.basicExecuteCall(AbstractSession.java:1717)
at org.eclipse.persistence.sessions.server.ServerSession.executeCall(ServerSession.java:566)
at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:207)
at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:193)
at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeSelectCall(DatasourceCallQueryMechanism.java:264)
at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.selectAllRows(DatasourceCallQueryMechanism.java:648)
at org.eclipse.persistence.internal.queries.ExpressionQueryMechanism.selectAllRowsFromTable(ExpressionQueryMechanism.java:2672)
at org.eclipse.persistence.internal.queries.ExpressionQueryMechanism.selectAllRows(ExpressionQueryMechanism.java:2631)
at org.eclipse.persistence.queries.ReadAllQuery.executeObjectLevelReadQuery(ReadAllQuery.java:420)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeDatabaseQuery(ObjectLevelReadQuery.java:1085)
at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:852)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.execute(ObjectLevelReadQuery.java:1044)
at org.eclipse.persistence.queries.ReadAllQuery.execute(ReadAllQuery.java:392)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeInUnitOfWork(ObjectLevelReadQuery.java:1132)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2871)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1519)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1501)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1466)
at org.eclipse.persistence.internal.jpa.QueryImpl.executeReadQuery(QueryImpl.java:231)
at org.eclipse.persistence.internal.jpa.QueryImpl.getResultList(QueryImpl.java:411)
Do you know why the "where" query is failing? Any suggestion?
Thanks
Giamma.
Developing for Virgo using PDE: http://bit.ly/1w0tTit
Global JNDI in Virgo: http://bit.ly/1to42mn
Hyperic to monitor Virgo: http://bit.ly/W1Fst9
Profile Virgo with JProfiler http://bit.ly/1FBLGCw
|
|
|
|
Re: Extensible Entities with extensions of type other than string [message #901708 is a reply to message #896559] |
Tue, 14 August 2012 09:16 |
GianMaria Romanato Messages: 72 Registered: July 2009 |
Member |
|
|
Hello everyone, it's working!
@Thomas:
your suggestion is really appreciated: the code snippet was wrong, I incorrectly wrote setName() but it should have been setClassName(). Thanks for pointing out the error, I edited the post so that it remains a good reference.
@Chris:
thank you very much, your guess was right.
I am now declaring all type converters on the XMLEntityMappings object:
TypeConverterMetadata conv = new TypeConverterMetadata();
conv.setName("IntegerConverter"); // converter name
conv.setObjectTypeName(Integer.class.getName()); // data type found in the object
conv.setDataTypeName(String.class.getName()); // data type of the database flex column, always String
mappings.getTypeConverters.add(conv);
conv = new TypeConverterMetadata();
conv.setName("BooleanConverter");
conv.setObjectTypeName(Boolean.class.getName());
conv.setDataTypeName(String.class.getName());
// and so on for all primitive types
and I am later referring to the appropriate converter by name when declaring the flex column:
BasicAccessor accessor = ...
entity.getAttributes().getBasics().add(accessor);
accessor.setName( attributeName );
ColumnMetadata cdata = new ColumnMetadata();
cdata.setName( columnName );
accessor.setColumn(cdata);
accessor.setConvert("IntegerConverter");
Thanks to everyone for the help.
Developing for Virgo using PDE: http://bit.ly/1w0tTit
Global JNDI in Virgo: http://bit.ly/1to42mn
Hyperic to monitor Virgo: http://bit.ly/W1Fst9
Profile Virgo with JProfiler http://bit.ly/1FBLGCw
|
|
|
Powered by
FUDForum. Page generated in 0.05955 seconds