You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@cayenne.apache.org by Hugi Thordarson <hu...@karlmenn.is> on 2019/04/04 10:09:30 UTC

Checking a value on a DataObject and modifying it before commit if required

Hi all,
I'm currently working on a legacy system. The DB has a lot of columns where null should actually be allowed, but instead those fields are non-nullable and get other values (empty string, "0", "-1" etc) written to them to indicate the absence of a value. Yay.

Unfortunately I can't just do the right thing; make the fields nullable and start writing nulls, since that would make other (non Cayenne) parts of the system explode, so I have to create a temporary workaround while I work through each column and make it nullable and fix (or kill) the legacy apps.

What I'd like to do is write my application logic as if the column was nullable, setting nulls when I want to, but when the object goes to the DB, "legacy nulls" are written to the DB instead (empty string or "-1" or whatever).

I have a working solution, I just wanted to check if you guys would have a better or more performant solution or see anything potentially wrong with what I'm doing.

What I'm doing is creating an interface (HasFictionalNullValues) which can be implemented by entity classes that require it. It defines a single method that can be implemented like…

public class SomeEntityClass extends _SomeEntityClass implements HasFictionalNullValues {

	@Override
	Map<Property,Object> fictionalNullValues) {
		Map<Property,Object map = new HashMap<>();
		map.put( NAME, "" );
		map.put( AGE, -1 );
		return map;
	}

	[...rest of class body...]
}

…then I add a listener to my DataDomain to catch new and updated objects

public class FictionalNullValuesListener {

	@PrePersist( { BaseDataObject.class } )
	@PreUpdate( { BaseDataObject.class } )
	public void onPrePersist( BaseDataObject object ) {
		if( object instanceof HasFictionalNullValues ) {
			((HasFictionalNullValues)object).fictionalNullValues().forEach( ( property, replacementNullValue ) -> {
				if( property.getFrom( object ) == null ) {
					property.setIn( object, replacementNullValue );
				}
			} );
		}
	}
}

Any comments on this practice or something I'm missing?

Cheers,
- hugi

Re: Checking a value on a DataObject and modifying it before commit if required

Posted by Hugi Thordarson <hu...@karlmenn.is>.
Thanks John. In this particular case, the only object modified is the object sent to the callback, so I'm probably safe in this case—but you probably saved my some-point-in-the-future ass with that valuable piece of info :).

Any thoughts on modifying the relationship join column without exposing it? Or is my only option to expose it and modify it on the entity class?

- hugi


> On 4 Apr 2019, at 12:31, John Huss <jo...@gmail.com> wrote:
> 
> There can be issues if you modify other objects inside lifecycle callbacks
> (a callback won't be called on that object). I would recommend doing the
> mods in validateForSave instead.
> 
> On Thu, Apr 4, 2019 at 6:44 AM Hugi Thordarson <hu...@karlmenn.is> wrote:
> 
>> Yes, this would have been a good idea :). Unfortunately the model
>> (containing dozens of tables and hundreds of relationships) is already
>> completely modeled using relationships and makes heavy use of them (for
>> expressions, ordering, prefetching etc. etc.)
>> 
>> The Cayenne-project was originally created as the reporting-part of the
>> system, so I didn't really hit these issues until now, when we're moving
>> the actual "write" part of the system there as well.
>> 
>> Dang…
>> 
>> Cheers,
>> - hugi
>> 
>> 
>> 
>>> On 4 Apr 2019, at 11:10, Ken Anderson <ke...@anderhome.com> wrote:
>>> 
>>> My guess would be “no”.
>>> 
>>> I suggest you don’t model the relationships until the foreign systems
>> are fixed or removed. In the meanwhile, I would implement methods as the
>> relationships, that would know if there’s a fictitious value or not and
>> return NULL for the relationship. When the other systems are fixed, remove
>> the method and implement the relationship normally.
>>> 
>>>> On Apr 4, 2019, at 6:39 AM, Hugi Thordarson <hu...@karlmenn.is> wrote:
>>>> 
>>>> I spoke a little too soon—my solution isn't sufficient, who'd have
>> thunk it :).
>>>> 
>>>> Some of the columns with "fictional null values" are used in
>> relationships. For example, I can have an invoice with customer_id "-1" to
>> indicate that there's no related customer.
>>>> 
>>>> As I'm done cleaning up the DB, these will eventually end up as actual
>> foreign keys with nulls instead of that -1, so I'd like to model the
>> relationship, hide the FK in the ObjEntity and use the "customer"
>> relationship (I've actually modeled it like that already and it works fine
>> for read operations, but of course everything explodes once I try to write
>> and object with a null customer to the DB and "customer_id" is set to null).
>>>> 
>>>> So, I kind of need to be doing this on the DbEntity level (if customer
>> is null, write "-1" to the customer_id).
>>>> 
>>>> Is this at all possible?
>>>> 
>>>> Cheers,
>>>> - hugi
>>>> 
>>>> 
>>>>> On 4 Apr 2019, at 10:09, Hugi Thordarson <hu...@karlmenn.is> wrote:
>>>>> 
>>>>> Hi all,
>>>>> I'm currently working on a legacy system. The DB has a lot of columns
>> where null should actually be allowed, but instead those fields are
>> non-nullable and get other values (empty string, "0", "-1" etc) written to
>> them to indicate the absence of a value. Yay.
>>>>> 
>>>>> Unfortunately I can't just do the right thing; make the fields
>> nullable and start writing nulls, since that would make other (non Cayenne)
>> parts of the system explode, so I have to create a temporary workaround
>> while I work through each column and make it nullable and fix (or kill) the
>> legacy apps.
>>>>> 
>>>>> What I'd like to do is write my application logic as if the column was
>> nullable, setting nulls when I want to, but when the object goes to the DB,
>> "legacy nulls" are written to the DB instead (empty string or "-1" or
>> whatever).
>>>>> 
>>>>> I have a working solution, I just wanted to check if you guys would
>> have a better or more performant solution or see anything potentially wrong
>> with what I'm doing.
>>>>> 
>>>>> What I'm doing is creating an interface (HasFictionalNullValues) which
>> can be implemented by entity classes that require it. It defines a single
>> method that can be implemented like…
>>>>> 
>>>>> public class SomeEntityClass extends _SomeEntityClass implements
>> HasFictionalNullValues {
>>>>> 
>>>>>    @Override
>>>>>    Map<Property,Object> fictionalNullValues) {
>>>>>            Map<Property,Object map = new HashMap<>();
>>>>>            map.put( NAME, "" );
>>>>>            map.put( AGE, -1 );
>>>>>            return map;
>>>>>    }
>>>>> 
>>>>>    [...rest of class body...]
>>>>> }
>>>>> 
>>>>> …then I add a listener to my DataDomain to catch new and updated
>> objects
>>>>> 
>>>>> public class FictionalNullValuesListener {
>>>>> 
>>>>>    @PrePersist( { BaseDataObject.class } )
>>>>>    @PreUpdate( { BaseDataObject.class } )
>>>>>    public void onPrePersist( BaseDataObject object ) {
>>>>>            if( object instanceof HasFictionalNullValues ) {
>>>>> 
>> ((HasFictionalNullValues)object).fictionalNullValues().forEach( (
>> property, replacementNullValue ) -> {
>>>>>                            if( property.getFrom( object ) == null ) {
>>>>>                                    property.setIn( object,
>> replacementNullValue );
>>>>>                            }
>>>>>                    } );
>>>>>            }
>>>>>    }
>>>>> }
>>>>> 
>>>>> Any comments on this practice or something I'm missing?
>>>>> 
>>>>> Cheers,
>>>>> - hugi
>>>> 
>>> 
>> 
>> 


Re: Checking a value on a DataObject and modifying it before commit if required

Posted by John Huss <jo...@gmail.com>.
There can be issues if you modify other objects inside lifecycle callbacks
(a callback won't be called on that object). I would recommend doing the
mods in validateForSave instead.

On Thu, Apr 4, 2019 at 6:44 AM Hugi Thordarson <hu...@karlmenn.is> wrote:

> Yes, this would have been a good idea :). Unfortunately the model
> (containing dozens of tables and hundreds of relationships) is already
> completely modeled using relationships and makes heavy use of them (for
> expressions, ordering, prefetching etc. etc.)
>
> The Cayenne-project was originally created as the reporting-part of the
> system, so I didn't really hit these issues until now, when we're moving
> the actual "write" part of the system there as well.
>
> Dang…
>
> Cheers,
> - hugi
>
>
>
> > On 4 Apr 2019, at 11:10, Ken Anderson <ke...@anderhome.com> wrote:
> >
> > My guess would be “no”.
> >
> > I suggest you don’t model the relationships until the foreign systems
> are fixed or removed. In the meanwhile, I would implement methods as the
> relationships, that would know if there’s a fictitious value or not and
> return NULL for the relationship. When the other systems are fixed, remove
> the method and implement the relationship normally.
> >
> >> On Apr 4, 2019, at 6:39 AM, Hugi Thordarson <hu...@karlmenn.is> wrote:
> >>
> >> I spoke a little too soon—my solution isn't sufficient, who'd have
> thunk it :).
> >>
> >> Some of the columns with "fictional null values" are used in
> relationships. For example, I can have an invoice with customer_id "-1" to
> indicate that there's no related customer.
> >>
> >> As I'm done cleaning up the DB, these will eventually end up as actual
> foreign keys with nulls instead of that -1, so I'd like to model the
> relationship, hide the FK in the ObjEntity and use the "customer"
> relationship (I've actually modeled it like that already and it works fine
> for read operations, but of course everything explodes once I try to write
> and object with a null customer to the DB and "customer_id" is set to null).
> >>
> >> So, I kind of need to be doing this on the DbEntity level (if customer
> is null, write "-1" to the customer_id).
> >>
> >> Is this at all possible?
> >>
> >> Cheers,
> >> - hugi
> >>
> >>
> >>> On 4 Apr 2019, at 10:09, Hugi Thordarson <hu...@karlmenn.is> wrote:
> >>>
> >>> Hi all,
> >>> I'm currently working on a legacy system. The DB has a lot of columns
> where null should actually be allowed, but instead those fields are
> non-nullable and get other values (empty string, "0", "-1" etc) written to
> them to indicate the absence of a value. Yay.
> >>>
> >>> Unfortunately I can't just do the right thing; make the fields
> nullable and start writing nulls, since that would make other (non Cayenne)
> parts of the system explode, so I have to create a temporary workaround
> while I work through each column and make it nullable and fix (or kill) the
> legacy apps.
> >>>
> >>> What I'd like to do is write my application logic as if the column was
> nullable, setting nulls when I want to, but when the object goes to the DB,
> "legacy nulls" are written to the DB instead (empty string or "-1" or
> whatever).
> >>>
> >>> I have a working solution, I just wanted to check if you guys would
> have a better or more performant solution or see anything potentially wrong
> with what I'm doing.
> >>>
> >>> What I'm doing is creating an interface (HasFictionalNullValues) which
> can be implemented by entity classes that require it. It defines a single
> method that can be implemented like…
> >>>
> >>> public class SomeEntityClass extends _SomeEntityClass implements
> HasFictionalNullValues {
> >>>
> >>>     @Override
> >>>     Map<Property,Object> fictionalNullValues) {
> >>>             Map<Property,Object map = new HashMap<>();
> >>>             map.put( NAME, "" );
> >>>             map.put( AGE, -1 );
> >>>             return map;
> >>>     }
> >>>
> >>>     [...rest of class body...]
> >>> }
> >>>
> >>> …then I add a listener to my DataDomain to catch new and updated
> objects
> >>>
> >>> public class FictionalNullValuesListener {
> >>>
> >>>     @PrePersist( { BaseDataObject.class } )
> >>>     @PreUpdate( { BaseDataObject.class } )
> >>>     public void onPrePersist( BaseDataObject object ) {
> >>>             if( object instanceof HasFictionalNullValues ) {
> >>>
>  ((HasFictionalNullValues)object).fictionalNullValues().forEach( (
> property, replacementNullValue ) -> {
> >>>                             if( property.getFrom( object ) == null ) {
> >>>                                     property.setIn( object,
> replacementNullValue );
> >>>                             }
> >>>                     } );
> >>>             }
> >>>     }
> >>> }
> >>>
> >>> Any comments on this practice or something I'm missing?
> >>>
> >>> Cheers,
> >>> - hugi
> >>
> >
>
>

Re: Checking a value on a DataObject and modifying it before commit if required

Posted by Hugi Thordarson <hu...@karlmenn.is>.
Yes, this would have been a good idea :). Unfortunately the model (containing dozens of tables and hundreds of relationships) is already completely modeled using relationships and makes heavy use of them (for expressions, ordering, prefetching etc. etc.)

The Cayenne-project was originally created as the reporting-part of the system, so I didn't really hit these issues until now, when we're moving the actual "write" part of the system there as well.

Dang…

Cheers,
- hugi



> On 4 Apr 2019, at 11:10, Ken Anderson <ke...@anderhome.com> wrote:
> 
> My guess would be “no”.
> 
> I suggest you don’t model the relationships until the foreign systems are fixed or removed. In the meanwhile, I would implement methods as the relationships, that would know if there’s a fictitious value or not and return NULL for the relationship. When the other systems are fixed, remove the method and implement the relationship normally.
> 
>> On Apr 4, 2019, at 6:39 AM, Hugi Thordarson <hu...@karlmenn.is> wrote:
>> 
>> I spoke a little too soon—my solution isn't sufficient, who'd have thunk it :).
>> 
>> Some of the columns with "fictional null values" are used in relationships. For example, I can have an invoice with customer_id "-1" to indicate that there's no related customer.
>> 
>> As I'm done cleaning up the DB, these will eventually end up as actual foreign keys with nulls instead of that -1, so I'd like to model the relationship, hide the FK in the ObjEntity and use the "customer" relationship (I've actually modeled it like that already and it works fine for read operations, but of course everything explodes once I try to write and object with a null customer to the DB and "customer_id" is set to null).
>> 
>> So, I kind of need to be doing this on the DbEntity level (if customer is null, write "-1" to the customer_id).
>> 
>> Is this at all possible?
>> 
>> Cheers,
>> - hugi
>> 
>> 
>>> On 4 Apr 2019, at 10:09, Hugi Thordarson <hu...@karlmenn.is> wrote:
>>> 
>>> Hi all,
>>> I'm currently working on a legacy system. The DB has a lot of columns where null should actually be allowed, but instead those fields are non-nullable and get other values (empty string, "0", "-1" etc) written to them to indicate the absence of a value. Yay.
>>> 
>>> Unfortunately I can't just do the right thing; make the fields nullable and start writing nulls, since that would make other (non Cayenne) parts of the system explode, so I have to create a temporary workaround while I work through each column and make it nullable and fix (or kill) the legacy apps.
>>> 
>>> What I'd like to do is write my application logic as if the column was nullable, setting nulls when I want to, but when the object goes to the DB, "legacy nulls" are written to the DB instead (empty string or "-1" or whatever).
>>> 
>>> I have a working solution, I just wanted to check if you guys would have a better or more performant solution or see anything potentially wrong with what I'm doing.
>>> 
>>> What I'm doing is creating an interface (HasFictionalNullValues) which can be implemented by entity classes that require it. It defines a single method that can be implemented like…
>>> 
>>> public class SomeEntityClass extends _SomeEntityClass implements HasFictionalNullValues {
>>> 
>>> 	@Override
>>> 	Map<Property,Object> fictionalNullValues) {
>>> 		Map<Property,Object map = new HashMap<>();
>>> 		map.put( NAME, "" );
>>> 		map.put( AGE, -1 );
>>> 		return map;
>>> 	}
>>> 
>>> 	[...rest of class body...]
>>> }
>>> 
>>> …then I add a listener to my DataDomain to catch new and updated objects
>>> 
>>> public class FictionalNullValuesListener {
>>> 
>>> 	@PrePersist( { BaseDataObject.class } )
>>> 	@PreUpdate( { BaseDataObject.class } )
>>> 	public void onPrePersist( BaseDataObject object ) {
>>> 		if( object instanceof HasFictionalNullValues ) {
>>> 			((HasFictionalNullValues)object).fictionalNullValues().forEach( ( property, replacementNullValue ) -> {
>>> 				if( property.getFrom( object ) == null ) {
>>> 					property.setIn( object, replacementNullValue );
>>> 				}
>>> 			} );
>>> 		}
>>> 	}
>>> }
>>> 
>>> Any comments on this practice or something I'm missing?
>>> 
>>> Cheers,
>>> - hugi
>> 
> 


Re: Checking a value on a DataObject and modifying it before commit if required

Posted by Ken Anderson <ke...@anderhome.com>.
My guess would be “no”.

I suggest you don’t model the relationships until the foreign systems are fixed or removed. In the meanwhile, I would implement methods as the relationships, that would know if there’s a fictitious value or not and return NULL for the relationship. When the other systems are fixed, remove the method and implement the relationship normally.

> On Apr 4, 2019, at 6:39 AM, Hugi Thordarson <hu...@karlmenn.is> wrote:
> 
> I spoke a little too soon—my solution isn't sufficient, who'd have thunk it :).
> 
> Some of the columns with "fictional null values" are used in relationships. For example, I can have an invoice with customer_id "-1" to indicate that there's no related customer.
> 
> As I'm done cleaning up the DB, these will eventually end up as actual foreign keys with nulls instead of that -1, so I'd like to model the relationship, hide the FK in the ObjEntity and use the "customer" relationship (I've actually modeled it like that already and it works fine for read operations, but of course everything explodes once I try to write and object with a null customer to the DB and "customer_id" is set to null).
> 
> So, I kind of need to be doing this on the DbEntity level (if customer is null, write "-1" to the customer_id).
> 
> Is this at all possible?
> 
> Cheers,
> - hugi
> 
> 
>> On 4 Apr 2019, at 10:09, Hugi Thordarson <hu...@karlmenn.is> wrote:
>> 
>> Hi all,
>> I'm currently working on a legacy system. The DB has a lot of columns where null should actually be allowed, but instead those fields are non-nullable and get other values (empty string, "0", "-1" etc) written to them to indicate the absence of a value. Yay.
>> 
>> Unfortunately I can't just do the right thing; make the fields nullable and start writing nulls, since that would make other (non Cayenne) parts of the system explode, so I have to create a temporary workaround while I work through each column and make it nullable and fix (or kill) the legacy apps.
>> 
>> What I'd like to do is write my application logic as if the column was nullable, setting nulls when I want to, but when the object goes to the DB, "legacy nulls" are written to the DB instead (empty string or "-1" or whatever).
>> 
>> I have a working solution, I just wanted to check if you guys would have a better or more performant solution or see anything potentially wrong with what I'm doing.
>> 
>> What I'm doing is creating an interface (HasFictionalNullValues) which can be implemented by entity classes that require it. It defines a single method that can be implemented like…
>> 
>> public class SomeEntityClass extends _SomeEntityClass implements HasFictionalNullValues {
>> 
>> 	@Override
>> 	Map<Property,Object> fictionalNullValues) {
>> 		Map<Property,Object map = new HashMap<>();
>> 		map.put( NAME, "" );
>> 		map.put( AGE, -1 );
>> 		return map;
>> 	}
>> 
>> 	[...rest of class body...]
>> }
>> 
>> …then I add a listener to my DataDomain to catch new and updated objects
>> 
>> public class FictionalNullValuesListener {
>> 
>> 	@PrePersist( { BaseDataObject.class } )
>> 	@PreUpdate( { BaseDataObject.class } )
>> 	public void onPrePersist( BaseDataObject object ) {
>> 		if( object instanceof HasFictionalNullValues ) {
>> 			((HasFictionalNullValues)object).fictionalNullValues().forEach( ( property, replacementNullValue ) -> {
>> 				if( property.getFrom( object ) == null ) {
>> 					property.setIn( object, replacementNullValue );
>> 				}
>> 			} );
>> 		}
>> 	}
>> }
>> 
>> Any comments on this practice or something I'm missing?
>> 
>> Cheers,
>> - hugi
> 


Re: Checking a value on a DataObject and modifying it before commit if required

Posted by Hugi Thordarson <hu...@karlmenn.is>.
I spoke a little too soon—my solution isn't sufficient, who'd have thunk it :).

Some of the columns with "fictional null values" are used in relationships. For example, I can have an invoice with customer_id "-1" to indicate that there's no related customer.

As I'm done cleaning up the DB, these will eventually end up as actual foreign keys with nulls instead of that -1, so I'd like to model the relationship, hide the FK in the ObjEntity and use the "customer" relationship (I've actually modeled it like that already and it works fine for read operations, but of course everything explodes once I try to write and object with a null customer to the DB and "customer_id" is set to null).

So, I kind of need to be doing this on the DbEntity level (if customer is null, write "-1" to the customer_id).

Is this at all possible?

Cheers,
- hugi


> On 4 Apr 2019, at 10:09, Hugi Thordarson <hu...@karlmenn.is> wrote:
> 
> Hi all,
> I'm currently working on a legacy system. The DB has a lot of columns where null should actually be allowed, but instead those fields are non-nullable and get other values (empty string, "0", "-1" etc) written to them to indicate the absence of a value. Yay.
> 
> Unfortunately I can't just do the right thing; make the fields nullable and start writing nulls, since that would make other (non Cayenne) parts of the system explode, so I have to create a temporary workaround while I work through each column and make it nullable and fix (or kill) the legacy apps.
> 
> What I'd like to do is write my application logic as if the column was nullable, setting nulls when I want to, but when the object goes to the DB, "legacy nulls" are written to the DB instead (empty string or "-1" or whatever).
> 
> I have a working solution, I just wanted to check if you guys would have a better or more performant solution or see anything potentially wrong with what I'm doing.
> 
> What I'm doing is creating an interface (HasFictionalNullValues) which can be implemented by entity classes that require it. It defines a single method that can be implemented like…
> 
> public class SomeEntityClass extends _SomeEntityClass implements HasFictionalNullValues {
> 
> 	@Override
> 	Map<Property,Object> fictionalNullValues) {
> 		Map<Property,Object map = new HashMap<>();
> 		map.put( NAME, "" );
> 		map.put( AGE, -1 );
> 		return map;
> 	}
> 
> 	[...rest of class body...]
> }
> 
> …then I add a listener to my DataDomain to catch new and updated objects
> 
> public class FictionalNullValuesListener {
> 
> 	@PrePersist( { BaseDataObject.class } )
> 	@PreUpdate( { BaseDataObject.class } )
> 	public void onPrePersist( BaseDataObject object ) {
> 		if( object instanceof HasFictionalNullValues ) {
> 			((HasFictionalNullValues)object).fictionalNullValues().forEach( ( property, replacementNullValue ) -> {
> 				if( property.getFrom( object ) == null ) {
> 					property.setIn( object, replacementNullValue );
> 				}
> 			} );
> 		}
> 	}
> }
> 
> Any comments on this practice or something I'm missing?
> 
> Cheers,
> - hugi