Re: Cross data map relationships

Ricardo Parada <[email protected]> Tue, 14 Oct 2025 19:13:24 -0400
Newsgroups gmane.comp.java.cayenne.user
Message-ID <[email protected]>
Hi Andrus,

I asked ChatGPT and it suggested adding the following two lines in addition t=
o creating the EntityResolver as you suggested.=20

var namespace =3D new EntityResolver(List.of(mainMap, errorsMap));
mainMap.setNamespace(namespace);
errorsMap.setNamespace(namespace);

Now it is generating the target attribute for the relationships in the XML.=20=


Thank you
Ricardo Parada




> On Oct 14, 2025, at 5:54=E2=80=AFPM, Andrus Adamchik <[email protected]>=
 wrote:
>=20
> =EF=BB=BFSee my follow up email :)
>=20
>> On Oct 14, 2025, at 5:51=E2=80=AFPM, Ricardo Parada <[email protected]=
LID> wrote:
>>=20
>> Andrus,
>>=20
>> What should I do then with the entity resolver?
>>=20
>> I created it like you said but it=E2=80=99s still not being used.
>>=20
>> Thank you
>> Ricardo Parada
>>=20
>>=20
>>=20
>>=20
>>>> On Oct 14, 2025, at 5:33=E2=80=AFPM, Andrus Adamchik <[email protected]=
om> wrote:
>>>=20
>>> =EF=BB=BF
>>>>=20
>>>> I then save the first  data map and it throws a NullPointerException. =20=

>>>=20
>>> Before save, try this to make sure entities can find each other across D=
ataMaps:
>>>=20
>>> EntityResolver namespace =3D new EntityResolver(List.of(mainMap, errorsM=
ap));
>>>=20
>>>> (I avoided creating data nodes and the project in this code to keep thi=
ngs simple and illustrate the NullPointerException)
>>>=20
>>> BTW, if you have a single DataNode, don't bother creating it in the mode=
l. It is much simpler and cleaner to just assign a DataSource when creating a=
 CayenneRuntime, and Cayenne will use it to handle all DataMaps in the proje=
ct.
>>>=20
>>> Andrus
>>>=20
>>>=20
>>>> On Oct 14, 2025, at 5:23=E2=80=AFPM, Ricardo Parada <[email protected]=
VALID> wrote:
>>>>=20
>>>> Here is a very minimal test of what I am seeing.
>>>>=20
>>>> Two data maps: Main and Errors.
>>>>=20
>>>> I have an APP_USER DbTable in the Main data map and an APP_ERROR DbTabl=
e in the Errors data map.
>>>>=20
>>>> Then I have a to-many relationship and inverse to one between these two=
.
>>>>=20
>>>> APP_USER <->> APP_ERROR
>>>>=20
>>>> I then simply create the ObjEntity, ObjAttribute and ObjRelationship ob=
jects programmatically.
>>>>=20
>>>> I then save the first  data map and it throws a NullPointerException. =20=

>>>>=20
>>>> Here is the isolated test:
>>>>=20
>>>> (I avoided creating data nodes and the project in this code to keep thi=
ngs simple and illustrate the NullPointerException)
>>>>=20
>>>> package play.cay.utils.conversion;
>>>>=20
>>>> import java.io.PrintWriter;
>>>> import java.sql.Types;
>>>>=20
>>>> import org.apache.cayenne.configuration.EmptyConfigurationNodeVisitor;
>>>> import org.apache.cayenne.map.DataMap;
>>>> import org.apache.cayenne.map.DbAttribute;
>>>> import org.apache.cayenne.map.DbEntity;
>>>> import org.apache.cayenne.map.DbJoin;
>>>> import org.apache.cayenne.map.DbRelationship;
>>>> import org.apache.cayenne.map.ObjAttribute;
>>>> import org.apache.cayenne.map.ObjEntity;
>>>> import org.apache.cayenne.map.ObjRelationship;
>>>> import org.apache.cayenne.util.XMLEncoder;
>>>>=20
>>>> public class DataMapTest {
>>>>=20
>>>> public static void main(String[] args) {
>>>>     // Create DataMaps
>>>>     DataMap mainMap =3D new DataMap("Main");
>>>>     DataMap errorsMap =3D new DataMap("Errors");
>>>>=20
>>>>     // -------------------- DB Entities --------------------
>>>>=20
>>>>     // APP_USER DbEntity
>>>>     DbEntity appUserDb =3D new DbEntity("APP_USER");
>>>>     DbAttribute appUserIdDbAttr =3D new DbAttribute("ID", Types.INTEGER=
, appUserDb);
>>>>     appUserIdDbAttr.setPrimaryKey(true);
>>>>     appUserDb.addAttribute(appUserIdDbAttr);
>>>>     appUserDb.addAttribute(new DbAttribute("NAME", Types.VARCHAR, appUs=
erDb));
>>>>     appUserDb.addAttribute(new DbAttribute("EMAIL", Types.VARCHAR, appU=
serDb));
>>>>     appUserDb.getPrimaryKeyGenerator();
>>>>=20
>>>>     // APP_ERROR DbEntity
>>>>     DbEntity appErrorDb =3D new DbEntity("APP_ERROR");
>>>>     DbAttribute appErrorIdDbAttr =3D new DbAttribute("ID", Types.INTEGE=
R, appErrorDb);
>>>>     appErrorDb.addAttribute(appErrorIdDbAttr);
>>>>     appErrorDb.addAttribute(new DbAttribute("APP_USER_ID", Types.INTEGE=
R, appErrorDb));
>>>>     appErrorDb.addAttribute(new DbAttribute("ERROR_MESSAGE", Types.VARC=
HAR, appErrorDb));
>>>>=20
>>>>     // Add DbEntities to respective maps
>>>>     mainMap.addDbEntity(appUserDb);
>>>>     errorsMap.addDbEntity(appErrorDb);
>>>>=20
>>>>     // -------------------- DB Relationships --------------------
>>>>=20
>>>>     // APP_ERROR -> APP_USER (to-one)
>>>>     DbRelationship toUserRel =3D new DbRelationship("appUser");
>>>>     toUserRel.setSourceEntity(appErrorDb);
>>>>     toUserRel.setTargetEntityName(appUserDb);
>>>>     toUserRel.setToMany(false);
>>>>     toUserRel.addJoin(new DbJoin(toUserRel, "APP_USER_ID", "ID"));
>>>>     appErrorDb.addRelationship(toUserRel);
>>>>=20
>>>>     // APP_USER ->> APP_ERROR (to-many)
>>>>     DbRelationship errorsRel =3D new DbRelationship("errors");
>>>>     errorsRel.setSourceEntity(appUserDb);
>>>>     errorsRel.setTargetEntityName(appErrorDb);
>>>>     errorsRel.setToMany(true);
>>>>     errorsRel.addJoin(new DbJoin(errorsRel, "ID", "APP_USER_ID"));
>>>>     appUserDb.addRelationship(errorsRel);
>>>>=20
>>>>     // -------------------- ObjEntities --------------------
>>>>=20
>>>>     // APP_USER ObjEntity
>>>>     ObjEntity appUserObj =3D new ObjEntity("AppUser");
>>>>     appUserObj.setDbEntity(appUserDb);
>>>>     appUserObj.addAttribute(new ObjAttribute("id", "java.lang.Integer",=
 appUserObj));
>>>>     appUserObj.addAttribute(new ObjAttribute("name", "java.lang.String"=
, appUserObj));
>>>>     appUserObj.addAttribute(new ObjAttribute("email", "java.lang.String=
", appUserObj));
>>>>=20
>>>>     // APP_ERROR ObjEntity
>>>>     ObjEntity appErrorObj =3D new ObjEntity("AppError");
>>>>     appErrorObj.setDbEntity(appErrorDb);
>>>>     appErrorObj.addAttribute(new ObjAttribute("id", "java.lang.Integer"=
, appErrorObj));
>>>>     appErrorObj.addAttribute(new ObjAttribute("errorMessage", "java.lan=
g.String", appErrorObj));
>>>>=20
>>>>     // Add ObjEntities to respective maps
>>>>     mainMap.addObjEntity(appUserObj);
>>>>     errorsMap.addObjEntity(appErrorObj);
>>>>=20
>>>>     // -------------------- ObjRelationships --------------------
>>>>=20
>>>>     // AppError =C3=A2=C2=86=C2=92 AppUser (to-one)
>>>>     ObjRelationship appUserRel =3D new ObjRelationship("appUser");
>>>>     appUserRel.setSourceEntity(appErrorObj);
>>>>     appUserRel.setTargetEntityName(appUserObj); // sets target name
>>>>     appUserRel.setDbRelationshipPath("appUser");
>>>>     appErrorObj.addRelationship(appUserRel);
>>>>=20
>>>>     // AppUser =C3=A2=C2=86=C2=92 AppError (to-many)
>>>>     ObjRelationship errorsRelObj =3D new ObjRelationship("errors");
>>>>     errorsRelObj.setSourceEntity(appUserObj);
>>>>     errorsRelObj.setTargetEntityName(appErrorObj); // sets target name
>>>>     errorsRelObj.setDbRelationshipPath("errors");
>>>>     appUserObj.addRelationship(errorsRelObj);
>>>>=20
>>>>     PrintWriter stdout =3D new PrintWriter(System.out, true);
>>>>     XMLEncoder encoder =3D new XMLEncoder(stdout, "\t", "11");
>>>>     EmptyConfigurationNodeVisitor visitor =3D new EmptyConfigurationNod=
eVisitor();
>>>>=20
>>>>     stdout.println(mainMap.getName());
>>>>     stdout.println();
>>>>     encoder.nested(mainMap, visitor);
>>>>=20
>>>>     stdout.println();
>>>>     stdout.println();
>>>>=20
>>>>     stdout.println(errorsMap.getName());
>>>>     stdout.println();
>>>>     encoder.nested(errorsMap, visitor);
>>>>=20
>>>> }
>>>> }
>>>>=20
>>>>=20
>>>>=20
>>>>>=20
>>>>>> On Oct 14, 2025, at 3:29=E2=80=AFPM, Ricardo Parada <[email protected]>=
 wrote:
>>>>>=20
>>>>> =EF=BB=BFHello,
>>>>>=20
>>>>> I wrote a converter to convert our eomodels from EOF to Cayenne-projec=
t.xml and data maps .map.xml
>>>>>=20
>>>>> So far so good. However, I seem to be running into a problem for cross=
 data map relationships.
>>>>>=20
>>>>> I=E2=80=99m using 5.0-M1.
>>>>>=20
>>>>> When I=E2=80=99m creating the ObjRelationship I call:
>>>>>=20
>>>>> objRel.setTargetEntityName(targetObjEntity);
>>>>>=20
>>>>> If I then call objRel.getTargetEntityName() it returns the correct tar=
get entity name. =20
>>>>>=20
>>>>> However, if I call objRel.getTargetEntity() it returns null.
>>>>>=20
>>>>> I debugged the code and I can see that getTargetEntity() is looking fo=
r the target entity name in the data map corresponding to the source ObjEnti=
ty. And it does not find it and returns null for that reason.
>>>>>=20
>>>>> As a result of that, when I call encodeAsXML() on objRel I see that th=
e XML is missing the target attribute in the obj-relationship xml tag.
>>>>>=20
>>>>> Is this a bug in 5.0-M1 or am I missing something when creating my Obj=
Relationship programmatically.
>>>>>=20
>>>>> I could put all eomodels that have cross eomodel relationships into th=
e same data map to avoid this problem.
>>>>>=20
>>>>> But when I create it in Cayenne Modeler 4.3.3 it seems to work. I see t=
he obj-relationship in the XML have target correctly set.
>>>>>=20
>>>>> Thank you all in advance.
>>>>> Ricardo Parada
>>>=20
>=20