Date   

Trouble understanding how to lookup objects that have a Datastore Identity by their PK (if the PK is not identified in the model)

ebenzacar@...
 

I'm migrating an application that was writing with Kodo4 (JDO 2.2).  Most of the objects/models use implicit datastore-identity with no fields identified in the model as the PK, but over time, developers have designed in systems to use the Object Ids (PK) to retrieve the objects by their ID.  I realize that this goes against the concept of datastore-identity and over time will need to change them.

In the meantime, however, I am having trouble figuring out how to retrieve an object using it's ID (Long) that is in the DB.

@PersistenceCapable
@DatastoreIdentity(strategy= IdGeneratorStrategy.SEQUENCE, sequence="toto" )
@Sequence(name="toto", datastoreSequence = "jdo_sequence", strategy=NONTRANSACTIONAL)
public class Organization {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}



Current code does the following:

public <T> T getObject( String spId, Class<T>persistentClass ) {
Object
id = pm.newObjectIdInstance(persistentClass, spId);
Object obj =
pm.getObjectById(id);
return persistentClass.cast(obj);
}

Unfortunately, with DataNucleus, this is not allowed.  When I try to call `pm.newObjectIdInstance( clazz, Long id )`, an exception is thrown by the ExecutionContext in org.datanucleus.ExecutionContextImpl#newObjectId(java.lang.Class, java.lang.Object) that the key is not a string and the class is not a SingleFieldIdentity.   If I convert the Long to a string, an exception is thrown that it essentially isn't in the right format.

I think I've hacked around the problem a bit by doing the following but it seems hackish - especially considering that I need to use a DN Core class as part of my code and not just leverage JDO methods
DatastoreId datastoreId = new DatastoreIdImplKodo(clazz.getName(), id);
Object oId = pm.newObjectIdInstance(Organization.class, datastoreId.toString()); return clazz.cast(pm.getObjectById(oId));


Is there no way to accomplish this more cleanly?  Can I identify the class/model differently such that it is identified as a SingleFieldIdentity class?

Thanks,

Eric


Re: Migration from Kodo: Any way to make it store foreign keys to later get already loaded objects from cache (for performance)?

Andy
 

Email is on the website for commercial support.

If you always have a single PK field then I don't see on the face of it why you need "PersistableIdMapping" (that mapping was an ugly hack that someone else introduced maybe 15 yrs ago and I only got as far as removing it from basic persistence operations, but not JDOQL as yet). If the application id you can look at the meta data and grab the metadata and mapping for the PK field. If datastore id you can look at the metadata and mapping for the surrogate field. But then without trying it and seeing examples of its "use" its impossible to say,


Re: Migration from Kodo: Any way to make it store foreign keys to later get already loaded objects from cache (for performance)?

dosiwelldi
 

Can you contact me in private? We are interested in professional DN support.

>> If you want the primary key as integer/long, are you talking about "application-id" or "datastore-id"?

I assume I need it for both since most of our application-id classes were datastore-id earlier, so some queries may still use JDOHelper.getObjectId() [or JDOHelper.getObjectIdHack() now] .

>> What happens when a class has a non-numeric PK field? or when it has a composite PK?

In my cases all are smallint, int or long.

>> There are many methods in the codebase that return NumericExpression that you can use as a guide to copy.

I looked at stuff. I think the "inner" numeric needs that PersistableIdMapping, and for creating the outer one as wrapper I only found the way I do it above. Will have another look at other code creating NumericExpression at another time.

But it seems to work so I think all is fine. I may later create a different constructor for NumericExpression (not sure if that needs changes in parent classes too). To create a nop-NumericExpression as wrapper to just change the mapping used.

I was not sure if you maybe say: that is a bug in the == code, it should not set the PersistableIdMapping on the int parameter. Because that code is too complex for my current level of understanding to just fiddle with.


Re: Sequence Support for MS SqlServer?

Andy
 

Not tested by me, as I don't have that database. As said many times, left for people to contribute


Re: Migration from Kodo: Any way to make it store foreign keys to later get already loaded objects from cache (for performance)?

Andy
 

If you want the primary key as integer/long, are you talking about "application-id" or "datastore-id"?
I have to assume "datastore-id" since if you have "application-id" and a single PK field of numeric type you don't need a method to get the id (field) as integer/long.

What happens when a class has a non-numeric PK field? or when it has a composite PK?

There are many methods in the codebase that return NumericExpression that you can use as a guide to copy.


Re: Is there a way to define a default sequence generator to use for all classes for Datastore-Identity instead of doing it on a per-class basis?

Andy
 

You can create a single sequence, and then specify on each class that you want to use it against whichever classes. There is no "apply this to everything unless told otherwise" option.

Why you would want to use a single sequence for many disparate classes is your issue.


Sequence Support for MS SqlServer?

ebenzacar@...
 

Hi,

According to the docs, there is support for Sequences for SQL Server.  I see it as well in the org.datanucleus.store.rdbms.adapter.SQLServerAdapter class.  However, when I configure my Model with the necessary sequence details, I get an exception thrown:

@PersistenceCapable
@DatastoreIdentity(strategy= IdGeneratorStrategy.SEQUENCE, sequence="toto" )
@Sequence(name="toto", datastoreSequence = "jdo_sequence", strategy=NONTRANSACTIONAL)
public class Organization {

private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
2021-03-10 21:32:53,085 DEBUG [http-nio-9080-exec-1] DataNucleus.Datastore.Native: SELECT NEXT VALUE FOR 'POC_DATANUCLEUS.DBO.JDO_SEQUENCE' 2021-03-10 21:32:53,092 DEBUG [http-nio-9080-exec-1] DataNucleus.Datastore: Closing PreparedStatement "org.datanucleus.store.rdbms.ParamLoggingPreparedStatement@4c38e3c6" 2021-03-10 21:32:53,092 INFO [http-nio-9080-exec-1] DataNucleus.ValueGeneration: Error encountered allocating block of IDs : Couldnt obtain a new sequence (unique id) : Incorrect syntax near 'POC_DATANUCLEUS.DBO.JDO_SEQUENCE'.

If I look in the code itself, I see the following comment on line 651:
// Do we need to quote the sequence name here?
StringBuilder stmt = new StringBuilder("SELECT NEXT VALUE FOR '");
stmt.append(sequenceName);
stmt.append("'");

From my quick tests, I see that the error is indeed caused by the 'quoting' of the sequence name.

This makes me wonder if it is my configuration which is incorrect, or if support for MS SQL Sequences has never really been tested?  Is there full support for SqlServer Sequences?  Is it just my configuration which is wrong?

Thanks,

Eric


Is there a way to define a default sequence generator to use for all classes for Datastore-Identity instead of doing it on a per-class basis?

ebenzacar@...
 

I've been reading through the docs for using native sequences for datastore-identities (https://www.datanucleus.org/products/accessplatform_5_0/jdo/mapping.html#valuegen_sequence).  But from what I can tell, I need to expressly identify for each persisted class that I want to use a sequence, and the name of the sequence I want to use.

If I want to use a single sequence for all my objects, is there a way to specify at the persistence manager level a default sequence generator to use instead?  I tried looking through all the pmf properties (https://www.datanucleus.org/products/accessplatform/jdo/persistence.html#pmf_properties) but couldn't find anything appropriate.

Is my only choice to manually annotate each class and hope no one forgets in the future?

Thanks,

Eric


Re: Migration from Kodo: Any way to make it store foreign keys to later get already loaded objects from cache (for performance)?

dosiwelldi
 

Hi again

I have a plugin for JDOHelper.getObjectIdHack. The aim is to get the primary key as integer/long in JDOQL.


    <extension point="org.datanucleus.store.rdbms.sql_method">
        <sql-method method="JDOHelper.getObjectIdHack" datastore="sqlserver"
            evaluator="xx.yy.datanucleus.GetObjectIdHackRdbms"/>
    </extension>

I worked for queries like 'JDOHelper.getObjectIdHack(ent) == 11'.
But now I found out it did not work for 'JDOHelper.getObjectIdHack(ent) == entId' where entId (int) is a parameter instead of a literal.

I found a solution for my problem, but it is a little ugly. I wanted to ask if I am just stupid or if I really need that workaround? And also is there a better way to wrap the NumericExpression?

The problem is that the mapping that is used in the NumericExpression that is the result of the custom function is applied to the variable entId too (because the code for == compares (and "corrects") the mappings). This will lead to a NPE then in PersistableIdMapping.setObject() because entId has no primary key [String[] pkMemberNames = cmd.getPrimaryKeyMemberNames()] but it got that mapping because of the code for ==.

My ugly fix is to wrap the NumericExpression in another NumericExpression, so only the inner one has PersistableIdMapping and ==-code will not see that.

Since I found no constructor to do that I now abuse the constructor for functions (with a function named " "):
    public NumericExpression(SQLStatement stmt, JavaTypeMapping mapping, String functionName, List args)
   

My initial variant that only worked for 'JDOHelper.getObjectIdHack(ent) == 11':

public class GetObjectIdHackRdbms implements SQLMethod {
    public SQLExpression getExpression(SQLStatement stmt, SQLExpression ignore, List<SQLExpression> args)
        ...
        if (expr.getJavaTypeMapping() instanceof PersistableMapping)
            JavaTypeMapping mapping = new PersistableIdMapping((PersistableMapping)expr.getJavaTypeMapping());
            return new NumericExpression(stmt, expr.getSQLTable(), mapping);
        }
    }
}

Current variant abusing NumericExpression for functions:

public class GetObjectIdHackRdbms implements SQLMethod {
    public SQLExpression getExpression(SQLStatement stmt, SQLExpression ignore, List<SQLExpression> args)
        ...
        if (expr.getJavaTypeMapping() instanceof PersistableMapping)
            JavaTypeMapping mapping = new PersistableIdMapping((PersistableMapping)expr.getJavaTypeMapping());
            JavaTypeMapping map_wrap = stmt.getSQLExpressionFactory().getMappingForType(Integer.class, true);
            NumericExpression tmp = new NumericExpression(stmt, expr.getSQLTable(), mapping);
            List<SQLExpression> tmpArgs = new ArrayList<>(1);
            tmpArgs.add(tmp);
            return new NumericExpression(stmt, map_wrap, " ", tmpArgs);
        }
    }
}

Thanks and kind regards


Re: Breaking change in CLOB columns in PostgreSQL coming from DN 5.2.2

keil@...
 

Hi Andy,

thanks for the clarification and the added note.

Cheers


Re: Breaking change in CLOB columns in PostgreSQL coming from DN 5.2.2

Andy
 

Both are stored as a "TEXT" column in the table.
Postgres doesnt support "CLOB" type in the same way as other RDBMS, so DN simply uses a "TEXT" when "CLOB" specified.
DataNucleus doesn't support non-standard, non-JDBC Postgres "large object" API usage.
The only difference is whether to access the "large string" column using JDBC LONGVARCHAR or the clob accessor. LongVarcharMapping makes more sense (to me), and that also means that if PostgreSQL ever bother to support JDBC CLOB using the JDBC API then it can handle both.
Hence no it is not a "bug" (to me). Now added to migration notes.


Re: Breaking change in CLOB columns in PostgreSQL coming from DN 5.2.2

keil@...
 

Hi Andy,

yes, as in the example project, the column is a String column annotated as CLOB with no further annotation information. With DN 5.2.2 this is stored in a text column using  a ClobColumnMapping and working fine. After the upgrade DN changes to using a LongVarcharColumn, which on itself is also working fine. The problem is with the transition that stops previous data from being readable.

The point with the requirements is perfectly reasonable from my point of view. I think also that both mappings are reasonable for storing String values. The change in the used default mapping was the surprise =). The question basically is: is the change in the default a bug or is it considered the correct default that I think should then be mentioned in the migration guide coming from 5.2.2?

Cheers,

   Christian


Re: Breaking change in CLOB columns in PostgreSQL coming from DN 5.2.2

Andy
 

If you are indeed seeing use of ClobColumnMapping on 5.2.2 and LongVarcharColumnMapping on 5.2.6 then you could add
@Column(jdbcType="CLOB",     extensions=@Extension(vendorName="datanucleus", key="column-mapping-class", value="org.datanucleus.store.rdbms.mapping.column.ClobColumnMapping"))
or
<column jdbc-type="CLOB">    <extension vendor-name="datanucleus" key="column-mapping-class" value="org.datanucleus.store.rdbms.mapping.column.ClobColumnMapping"/></column>

which should override any internally chosen column mapping class.


Re: JPA Multitenancy delete doesn't use tenant-read-ids

stephane
 

As tenant-read-ids is not used for UPDATES, but is used for DELETES I've thought something was forgotten... 

The feature I'm putting in place is to "partition" some data. Then users can access to the data of all the partitions they belong to.

So Multitenancy seems to perfectly fulfill the partitioning and the tenant-read-ids features potentially offer a great flexibility, but, flexibility bring big complexities which I want to evaluate:
- How select,insert,update,delete works on simple objects 
- How do they works on relationship loading
- How L2 cache behave with this feature ?
...


Re: JPA Multitenancy delete doesn't use tenant-read-ids

Andy
 

A DELETE is not a READ. Hence "tenantReadIds" is not for use on a DELETE (or on an INSERT, or an UPDATE). That is the DataNucleus feature definition.
Doesn't mean that is the feature you want.


Re: JPA TestCase for MultiTenancy

Andy
 

Some column is NULL on an SQL statement (INSERT/UPDATE) and shouldn't be. Only you can see the SQL statement issued that causes that SQL exception, and a simple look at the table DDL would reveal why that exception would be thrown


JPA Multitenancy delete doesn't use tenant-read-ids

stephane
 

Hi,

When removing an object, DN only uses TenantId, while it uses the tenant-read-ids when querying.

// Tenants : John,Chris
final
Query query = entityManager.createQuery("select e from Element e ");
final List resultList = query.getResultList();
for (Object o : resultList)
{
entityManager.remove(o);
}
transaction.commit();

SELECT 'net.nature.Element' AS `DN_TYPE`,`E`.`ID`,`E`.`NAME` FROM `ELEMENT` `E` WHERE `E`.`TENANT` IN('John','Chris')
DELETE FROM `ELEMENT` WHERE `ID`=<1614949869149> AND `TENANT`=<'NULL'> ...


As I haven't found in the docs yet how it should work.

The DELETE should be :
1- if Tenant is not part of the identifier and we somewhat strict:
DELETE FROM `ELEMENT` WHERE `ID`=<1614949869149> AND `TENANT`IN('John','Chris') ...
2- if looking at the value of the tenant of this object, we can be very strict
DELETE FROM `ELEMENT` WHERE `ID`=<1614949869149> AND `TENANT`= 'John'
3- if we're a little less strict and tenant is not part of the id (just as it is for updates):
DELETE FROM `ELEMENT` WHERE `ID`=<1614949869149>
Update example:
UPDATE `ELEMENT` SET `NAME`=<'Aluminum 1614949877211 1614950377268'> WHERE `ID`=<1614949869149>

I think DeleteRequest can make use of getTenantReadIds while it only uses getTenantId.
if (multitenancyStatementMapping != null)
{
table.getSurrogateMapping(SurrogateColumnType.MULTITENANCY, false).setObject(ec, ps,
multitenancyStatementMapping.getParameterPositionsForOccurrence(0), ec.getNucleusContext().getMultiTenancyId(ec));
}

Considering the case 3, may mean to remove the code above... 

--
Stephane


Re: Multi Tenancy : find fails with a NPE on DN 2.5.6

stephane
 
Edited

Miss read the doc. TenantProvider need to be an object in the EntityManager setting.

Map map = new HashMap();
map.put(PropertyNames.PROPERTY_MAPPING_TENANT_PROVIDER, new MyTenantProvider());
final EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("APP_UNIT", map);

So far TenantProvider works with JPA, remains to understand why TenantReadIds on the EntityManger doesn't...
--
Stephane


JPA TestCase for MultiTenancy

stephane
 

Hi,
 
As you can see on other topics, I'm facing some difficulties with MultiTenancy in JPA. I haven't diagnose yet why settings are not applied but I translated the JDO TestCase to JPA. 
These tests doesn't run with this error:
java.sql.BatchUpdateException: integrity constraint violation: NOT NULL check constraint; SYS_CT_10117 table: TENANTEDOBJECT column: TENANT
 
Can you have a look ? 

thanks
 

Here is a Patch for JPA general testsuite:


Index: jpa/general/src/java/META-INF/persistence.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/jpa/general/src/java/META-INF/persistence.xml b/jpa/general/src/java/META-INF/persistence.xml
--- a/jpa/general/src/java/META-INF/persistence.xml (revision 77f14e53f198cbf4cf728da2a88cc41188470547)
+++ b/jpa/general/src/java/META-INF/persistence.xml (date 1614931959201)
@@ -139,6 +139,7 @@
         <class>org.datanucleus.samples.typeconversion.ComplicatedType2</class>
         <class>org.datanucleus.samples.annotations.one_many.map_keyclass.MapHolderWithKeyClass</class>
         <class>org.datanucleus.samples.annotations.one_many.map_keyclass.MapKeyClassTarget</class>
+        <class>org.datanucleus.samples.jpa.multitenancy.TenantedObject</class>
     </persistence-unit>
 
 </persistence>
Index: jpa/general/src/java/org/datanucleus/samples/jpa/multitenancy/TenantedObject.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/jpa/general/src/java/org/datanucleus/samples/jpa/multitenancy/TenantedObject.java b/jpa/general/src/java/org/datanucleus/samples/jpa/multitenancy/TenantedObject.java
new file mode 100644
--- /dev/null (date 1614931840553)
+++ b/jpa/general/src/java/org/datanucleus/samples/jpa/multitenancy/TenantedObject.java (date 1614931840553)
@@ -0,0 +1,30 @@
+package org.datanucleus.samples.jpa.multitenancy;
+
+import org.datanucleus.api.jpa.annotations.MultiTenant;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+@MultiTenant(column = "TENANT", columnLength = 24)
+public class TenantedObject {
+ @Id
+ long id;
+ String name;
+
+ public long getId() {
+ return this.id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
Index: jpa/general/src/test/org/datanucleus/tests/MultitenancyTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/jpa/general/src/test/org/datanucleus/tests/MultitenancyTest.java b/jpa/general/src/test/org/datanucleus/tests/MultitenancyTest.java
new file mode 100644
--- /dev/null (date 1614934980017)
+++ b/jpa/general/src/test/org/datanucleus/tests/MultitenancyTest.java (date 1614934980017)
@@ -0,0 +1,528 @@
+/**********************************************************************
+ Copyright (c) 2021 Andy Jefferson and others. All rights reserved.
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Contributors:
+ ...
+ **********************************************************************/
+package org.datanucleus.tests;
+
+import org.datanucleus.PropertyNames;
+import org.datanucleus.api.jpa.NucleusJPAHelper;
+import org.datanucleus.samples.jpa.multitenancy.TenantedObject;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.EntityTransaction;
+import javax.validation.ConstraintViolationException;
+import java.util.Properties;
+
+/**
+ * Tests for multitenancy with JDO.
+ */
+public class MultitenancyTest extends JPAPersistenceTestCase {
+    private static boolean initialised = false;
+
+    /**
+     * Constructor.
+     *
+     * @param name Name of the test (not used)
+     */
+    public MultitenancyTest(String name) {
+        super(name);
+        if (!initialised)
+        {
+            addClassesToSchema(new Class[]{TenantedObject.class});
+            initialised = true;
+        }
+    }
+
+    /**
+     * Test basic tenantId specification via persistence property
+     */
+    public void testTenantId() {
+        Properties userProps = new Properties();
+        userProps.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID");
+        EntityManagerFactory emfTenant1 = getEMF(1, "JPATest", userProps);
+        Properties userProps2 = new Properties();
+        userProps2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID2");
+        EntityManagerFactory emfTenant2 = getEMF(1, "JPATest", userProps2);
+        try
+        {
+            // Persist object for first tenant
+            EntityManager em1 = emfTenant1.createEntityManager();
+            EntityTransaction tx1 = em1.getTransaction();
+            Object id1 = null;
+            try
+            {
+                tx1.begin();
+                TenantedObject o1 = new TenantedObject();
+                o1.setId(1);
+                o1.setName("First");
+                em1.persist(o1);
+                tx1.commit();
+                id1 = o1.getId();
+                ;
+            } catch (ConstraintViolationException cve)
+            {
+                // expected
+                LOG.info("Exception correctly thrown : " + cve.getMessage());
+            } finally
+            {
+                if (tx1.isActive())
+                {
+                    tx1.rollback();
+                }
+                em1.close();
+            }
+            // Persist object for second tenant
+            EntityManager em2 = emfTenant2.createEntityManager();
+            EntityTransaction tx2 = em2.getTransaction();
+            Object id2 = null;
+            try
+            {
+                tx2.begin();
+                TenantedObject o2 = new TenantedObject();
+                o2.setId(2);
+                o2.setName("Second");
+                em2.persist(o2);
+                tx2.commit();
+                id2 = o2.getId();
+            } catch (ConstraintViolationException cve)
+            {
+                // expected
+                LOG.info("Exception correctly thrown : " + cve.getMessage());
+            } finally
+            {
+                if (tx2.isActive())
+                {
+                    tx2.rollback();
+                }
+                em2.close();
+            }
+            // Retrieve all objects for first tenant
+            em1 = emfTenant1.createEntityManager();
+            tx1 = em1.getTransaction();
+            try
+            {
+                tx1.begin();
+                // Check basic retrieve of expect object
+                TenantedObject obj1 = (TenantedObject) em1.find(TenantedObject.class, id1);
+                assertNotNull(obj1);
+                TenantedObject obj2 = em1.find(TenantedObject.class, id2);
+                assertNull("Returned object 2 from tenant 1 but shouldn't have", obj2);
+                //                    JDOObjectNotFoundException
+                tx1.commit();
+            } finally
+            {
+                if (tx1.isActive())
+                {
+                    tx1.rollback();
+                }
+                em1.close();
+            }
+            // Retrieve all objects for second tenant
+            em2 = emfTenant2.createEntityManager();
+            tx2 = em2.getTransaction();
+            try
+            {
+                tx2.begin();
+                // Check basic retrieve of expect object
+                TenantedObject obj2 = em2.find(TenantedObject.class, id2);
+                assertNotNull(obj2);
+                TenantedObject obj1 = em2.find(TenantedObject.class, id1);
+                assertNull("Returned object 1 from tenant 2 but shouldn't have", obj1);
+                tx2.commit();
+            } finally
+            {
+                if (tx2.isActive())
+                {
+                    tx2.rollback();
+                }
+                em2.close();
+            }
+        } finally
+        {
+            // Clear data
+            clean(emfTenant1, TenantedObject.class);
+            clean(emfTenant2, TenantedObject.class);
+            // Close PMFs
+            emfTenant1.close();
+            emfTenant2.close();
+        }
+    }
+    /**
+     * Test tenantId specification via MultitenancyProvider
+     */
+    // TODO
+
+    /**
+     * Test tenantReadIds specification via persistence property
+     */
+    public void testTenantIdWithReadIds() {
+        Properties userProps = new Properties();
+        userProps.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID");
+        userProps.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_READ_IDS, "MyID,MyID2");
+        EntityManagerFactory pmfTenant1 = getEMF(1, "JPATest", userProps);
+        Properties userProps2 = new Properties();
+        userProps2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID2");
+        userProps2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_READ_IDS, "MyID,MyID2");
+        EntityManagerFactory pmfTenant2 = getEMF(1, "JPATest", userProps2);
+        try
+        {
+            // Persist object for first tenant
+            EntityManager em1 = pmfTenant1.createEntityManager();
+            EntityTransaction tx1 = em1.getTransaction();
+            Object id1 = null;
+            try
+            {
+                tx1.begin();
+                TenantedObject o1 = new TenantedObject();
+                o1.setId(1);
+                o1.setName("First");
+                em1.persist(o1);
+                tx1.commit();
+                id1 = o1.getId();
+                ;
+            } catch (ConstraintViolationException cve)
+            {
+                // expected
+                LOG.info("Exception correctly thrown : " + cve.getMessage());
+            } finally
+            {
+                if (tx1.isActive())
+                {
+                    tx1.rollback();
+                }
+                em1.close();
+            }
+            // Persist object for second tenant
+            EntityManager em2 = pmfTenant2.createEntityManager();
+            EntityTransaction tx2 = em2.getTransaction();
+            Object id2 = null;
+            try
+            {
+                tx2.begin();
+                TenantedObject o2 = new TenantedObject();
+                o2.setId(2);
+                o2.setName("Second");
+                em2.persist(o2);
+                tx2.commit();
+                id2 = o2.getId();
+                ;
+            } catch (ConstraintViolationException cve)
+            {
+                // expected
+                LOG.info("Exception correctly thrown : " + cve.getMessage());
+            } finally
+            {
+                if (tx2.isActive())
+                {
+                    tx2.rollback();
+                }
+                em2.close();
+            }
+            // Retrieve all objects for first tenant
+            em1 = pmfTenant1.createEntityManager();
+            tx1 = em1.getTransaction();
+            try
+            {
+                tx1.begin();
+                // Check basic retrieve of expect objects
+                TenantedObject obj1 = em1.find(TenantedObject.class,id1);
+                assertNotNull(obj1);
+                TenantedObject obj2 =  em1.find(TenantedObject.class,id2);
+                assertNotNull(obj2);
+                tx1.commit();
+            } finally
+            {
+                if (tx1.isActive())
+                {
+                    tx1.rollback();
+                }
+                em1.close();
+            }
+            // Retrieve all objects for second tenant
+            em2 = pmfTenant2.createEntityManager();
+            tx2 = em2.getTransaction();
+            try
+            {
+                tx2.begin();
+                // Check basic retrieve of expect objects
+                TenantedObject obj1 = em2.find(TenantedObject.class,id1);
+                assertNotNull(obj1);
+                TenantedObject obj2 = em2.find(TenantedObject.class,id2);
+                assertNotNull(obj2);
+                tx2.commit();
+            } finally
+            {
+                if (tx2.isActive())
+                {
+                    tx2.rollback();
+                }
+                em2.close();
+            }
+        } finally
+        {
+            // Clear data
+            clean(pmfTenant1, TenantedObject.class);
+            clean(pmfTenant2, TenantedObject.class);
+            // Close PMFs
+            pmfTenant1.close();
+            pmfTenant2.close();
+        }
+    }
+    /**
+     * Test tenantReadIds via EntityManager
+     */
+    public void testTenantIdViaEntityManager() {
+        Properties userProps = new Properties();
+//        userProps.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID");
+        EntityManagerFactory emfTenant1 = getEMF(1, "JPATest", userProps);
+        Properties userProps2 = new Properties();
+//        userProps2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID2");
+        EntityManagerFactory emfTenant2 = getEMF(1, "JPATest", userProps2);
+        try
+        {
+            // Persist object for first tenant
+            EntityManager em1 = emfTenant1.createEntityManager();
+            em1.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID");
+            EntityTransaction tx1 = em1.getTransaction();
+            Object id1 = null;
+            try
+            {
+                tx1.begin();
+                TenantedObject o1 = new TenantedObject();
+                o1.setId(1);
+                o1.setName("First");
+                em1.persist(o1);
+                tx1.commit();
+                id1 = o1.getId();
+                ;
+            } catch (ConstraintViolationException cve)
+            {
+                // expected
+                LOG.info("Exception correctly thrown : " + cve.getMessage());
+            } finally
+            {
+                if (tx1.isActive())
+                {
+                    tx1.rollback();
+                }
+                em1.close();
+            }
+            // Persist object for second tenant
+            EntityManager em2 = emfTenant2.createEntityManager();
+            em2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID2");
+            EntityTransaction tx2 = em2.getTransaction();
+            Object id2 = null;
+            try
+            {
+                tx2.begin();
+                TenantedObject o2 = new TenantedObject();
+                o2.setId(2);
+                o2.setName("Second");
+                em2.persist(o2);
+                tx2.commit();
+                id2 = o2.getId();
+            } catch (ConstraintViolationException cve)
+            {
+                // expected
+                LOG.info("Exception correctly thrown : " + cve.getMessage());
+            } finally
+            {
+                if (tx2.isActive())
+                {
+                    tx2.rollback();
+                }
+                em2.close();
+            }
+            // Retrieve all objects for first tenant
+            em1 = emfTenant1.createEntityManager();
+            em1.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID");
+            tx1 = em1.getTransaction();
+            try
+            {
+                tx1.begin();
+                // Check basic retrieve of expect object
+                TenantedObject obj1 = (TenantedObject) em1.find(TenantedObject.class, id1);
+                assertNotNull(obj1);
+                TenantedObject obj2 = em1.find(TenantedObject.class, id2);
+                assertNull("Returned object 2 from tenant 1 but shouldn't have", obj2);
+                //                    JDOObjectNotFoundException
+                tx1.commit();
+            } finally
+            {
+                if (tx1.isActive())
+                {
+                    tx1.rollback();
+                }
+                em1.close();
+            }
+            // Retrieve all objects for second tenant
+            em2 = emfTenant2.createEntityManager();
+            em2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID2");
+            tx2 = em2.getTransaction();
+            try
+            {
+                tx2.begin();
+                // Check basic retrieve of expect object
+                TenantedObject obj2 = em2.find(TenantedObject.class, id2);
+                assertNotNull(obj2);
+                TenantedObject obj1 = em2.find(TenantedObject.class, id1);
+                assertNull("Returned object 1 from tenant 2 but shouldn't have", obj1);
+                tx2.commit();
+            } finally
+            {
+                if (tx2.isActive())
+                {
+                    tx2.rollback();
+                }
+                em2.close();
+            }
+        } finally
+        {
+            // Clear data
+            clean(emfTenant1, TenantedObject.class);
+            clean(emfTenant2, TenantedObject.class);
+            // Close PMFs
+            emfTenant1.close();
+            emfTenant2.close();
+        }
+    }
+    public void testTenantIdWithReadIdsViaEntityManager() {
+        Properties userProps = new Properties();
+//        userProps.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID");
+//        userProps.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_READ_IDS, "MyID,MyID2");
+        EntityManagerFactory pmfTenant1 = getEMF(1, "JPATest", userProps);
+        Properties userProps2 = new Properties();
+//        userProps2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID2");
+//        userProps2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_READ_IDS, "MyID,MyID2");
+        EntityManagerFactory pmfTenant2 = getEMF(1, "JPATest", userProps2);
+        try
+        {
+            // Persist object for first tenant
+            EntityManager em1 = pmfTenant1.createEntityManager();
+                    em1.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID");
+                   em1.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_READ_IDS, "MyID,MyID2");
+            EntityTransaction tx1 = em1.getTransaction();
+            Object id1 = null;
+            try
+            {
+                tx1.begin();
+                TenantedObject o1 = new TenantedObject();
+                o1.setId(1);
+                o1.setName("First");
+                em1.persist(o1);
+                tx1.commit();
+                id1 = o1.getId();
+                ;
+            } catch (ConstraintViolationException cve)
+            {
+                // expected
+                LOG.info("Exception correctly thrown : " + cve.getMessage());
+            } finally
+            {
+                if (tx1.isActive())
+                {
+                    tx1.rollback();
+                }
+                em1.close();
+            }
+            // Persist object for second tenant
+            EntityManager em2 = pmfTenant2.createEntityManager();
+            em2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID2");
+            em2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_READ_IDS, "MyID,MyID2");
+            EntityTransaction tx2 = em2.getTransaction();
+            Object id2 = null;
+            try
+            {
+                tx2.begin();
+                TenantedObject o2 = new TenantedObject();
+                o2.setId(2);
+                o2.setName("Second");
+                em2.persist(o2);
+                tx2.commit();
+                id2 = o2.getId();
+                ;
+            } catch (ConstraintViolationException cve)
+            {
+                // expected
+                LOG.info("Exception correctly thrown : " + cve.getMessage());
+            } finally
+            {
+                if (tx2.isActive())
+                {
+                    tx2.rollback();
+                }
+                em2.close();
+            }
+            // Retrieve all objects for first tenant
+            em1 = pmfTenant1.createEntityManager();
+            em1.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID");
+            tx1 = em1.getTransaction();
+            try
+            {
+                tx1.begin();
+                // Check basic retrieve of expect objects
+                TenantedObject obj1 = em1.find(TenantedObject.class,id1);
+                assertNotNull(obj1);
+                TenantedObject obj2 =  em1.find(TenantedObject.class,id2);
+                assertNotNull(obj2);
+                tx1.commit();
+            } finally
+            {
+                if (tx1.isActive())
+                {
+                    tx1.rollback();
+                }
+                em1.close();
+            }
+            // Retrieve all objects for second tenant
+            em2 = pmfTenant2.createEntityManager();
+            em2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID2");
+            em2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_READ_IDS, "MyID,MyID2");
+            tx2 = em2.getTransaction();
+            try
+            {
+                tx2.begin();
+                // Check basic retrieve of expect objects
+                TenantedObject obj1 = em2.find(TenantedObject.class,id1);
+                assertNotNull(obj1);
+                TenantedObject obj2 = em2.find(TenantedObject.class,id2);
+                assertNotNull(obj2);
+                tx2.commit();
+            } finally
+            {
+                if (tx2.isActive())
+                {
+                    tx2.rollback();
+                }
+                em2.close();
+            }
+        } finally
+        {
+            // Clear data
+            clean(pmfTenant1, TenantedObject.class);
+            clean(pmfTenant2, TenantedObject.class);
+            // Close PMFs
+            pmfTenant1.close();
+            pmfTenant2.close();
+        }
+    }
+    /**
+     * Test tenantReadIds specification via MultitenancyProvider
+     */
+    // TODO
+
+}
\ No newline at end of file
 
 
 
--
Stephane


Re: Error when using the jdbcType NVARCHAR with H2

mwhesse@...
 

Ok, thanks Andy!

Will change the local type as suggested.

41 - 60 of 353