-
-
Couldn't load subscription status.
- Fork 3.7k
Mutability consistency #6090
-
Hibernate has different notions of (im)mutability:
- Whether the internal state of a value can change. E.g. a Java
Dateis mutable because one can change its internal state viaDate#setTime. AString, however, is immutable because there is no way to change its internal state. This sense describes part of the metadata for the Java model and has an affect on dirty check, caching and making copies. - Whether the columns of the attribute are updateable. In this sense, it is a convenience for
@Column(updatable=true/false)for all associated columns. This sense is a function of the O/R mapping.
For the rest of the Discussion, to avoid confusion, let's call (1) mutability and (2) updateability.
Historically Hibernate had @Immutable. It was actually never well defined which of the two @Immutable covered; but, as @Immutable was only allowed on an entity, this distinction was not super important - an entity we are told is immutable is naturally non-updateable as well since we never update those columns.
In 6.0 we introduced MutabilityPlan and @Mutability which explicitly deal with (1). @Mutability is allowed on attributes, though not on an entity.
In 6.2 (non-Final atm) we have added the ability to apply @Immutable on attributes. The current effect of that is that the attribute is considered both immutable (1) and non-updateable (2).
The consistency problem is that currently @Immutable sometimes means (1), sometimes (2) and sometimes both depending on where it is used. I saw a few options to achieve this consistency -
- In a clean-room, I think what makes the most sense is to consider
@Immutablea shorthand for an immutableMutabilityPlan. For the notion of updateability I would have either relied on@Columnor introduced a new annotation (@Updateable?) specifically for case (2). Keeping in mind that allowing@Immutableon attributes is not in a Final release, there is no backwards compatibility concern here. I also like that the names actually reflect the intent. - An option we discussed on https://hibernate.zulipchat.com/#narrow/stream/132094-hibernate-orm-dev/topic/.40Immutable.20and.20AttributeConverter[Zulip] was to consider
@Immutableas always meaning both. The case of immutable but updateable would have to explicitly use@Mutability. This has a big impact, including some backwards compatibility concerns.
Where each is valid
Each of @Immutable and @Mutability (and @Updateable if we elect that option) are valid or invalid based on where they are specified:
Entity
Historically, Hibernate has allowed @Immutable on an entity. I think the effect there is perfectly fine. @Mutability is never appropriate on an entity; Hibernate has very specific, internal requirements for the MutabilityPlan for an entity.
With either of the 2 options I listed:
@Immutablewould remain valid on the root of an entity hierarchy and continues to mean that Hibernate ignores any attribute state changes to the entities in that hierarchy. It effectively becomes read only.@Mutabilitywould remain invalid on an entity
Attribute
An attribute can be either, neither or both. It is mutable/immutable independent of being updateable/non-updateable.
Non-updateable implies immutable.
Generally the mutability of an attribute is dictated by its Java type, as in the Date / String discussion. There are times a user might want to make an attribute, with an inherently mutable type (e.g. Date), immutable. E.g. this has some performance gains for the expectation that Hibernate ignores internal state changes.
A non-updateable attribute means that none of the columns associated with the attribute are considered updateable. Note that the effective impact of this might vary depending on the type of attribute (basic versus many-to-one e.g.).
Again, this was added in 6.2 which is not yet Final, so we have some leeway here with regard to backwards compatibility.
Currently:
@Immutableis allowed and is shorthand for@Mutability(Immutability.class)plusColumn(..., updatable=false)on all columns associated with the attribute.@Mutabilityis allowed, supplying a customMutabilityPlan
With the clean room option:
@Immutableis allowed and only affects mutability - shorthand for@Mutability(Immutability.class)@Mutabilityis allowed, supplying a customMutabilityPlan@Updateable(false)is allowed and is shorthand forColumn(..., updatable=false)on all columns associated with the attribute
With the Zulip option:
@Immutableis allowed and is shorthand for@Mutability(Immutability.class)plusColumn(..., updatable=false)on all columns associated with the attribute (tbf I only verified basic). This is how it worked in the previous 6.2 CR releases.@Mutabilityis allowed, supplying a customMutabilityPlan- There is no
@Updateable.@Updateable(false)is accomplished with@Immutable
See also the discussions below about AttributeConverter and UserType as they ultimately apply to attributes.
AttributeConverter
An AttributeConverter can be marked as immutable which implies immutability to every attribute it is applied to. It is not reasonable for an AttributeConverter to be marked as non-updateable.
With the clean room option:
@Immutableis allowed on the converter class and is shorthand for@Mutability(Immutability.class)@Mutabilityis allowed, supplying a customMutabilityPlanthat is applied to attributes the converter is applied to@Updateableis not allowed
With Zulip option:
@Immutableis not allowed on the converter class.@Mutabilityis allowed, supplying a customMutabilityPlanthat is applied to attributes the converter is applied to
UserType
Same as AttributeConverter
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 4 comments 25 replies
-
I may be misunderstanding something, but in my mind immutability may imply non-updateability, but not always. In theory, and lacking any input from users, updateability would be a direct consequence of "aggregated" immutability...
What I mean is, for example, with a model like this:
public class MyEntity {
@Embedded
private MyEmbeddable embedded;
}
public class MyEmbeddable {
private Date myDate;
}
Then if all of the following are considered immutable:
MyEntity#embeddableMyEmbeddable#myDateDate(or, if there is aUserType/AttributeConverter, that type/converter)
... we can safely consider the column(s) bound to embeddable.myDate as non-updateable. If any of the above is considered mutable, then we might need to update the column(s) bound to embeddable.myDate, so they should be updateable.
Though I'll admit I don't fully grasp what's at stake here. In particular, I don't understand why one would want to force non-updateability on a mutable attribute/type... does that even make sense? Why not declare that attribute/type immutable?
Beta Was this translation helpful? Give feedback.
All reactions
-
Ok, so back to my question: what is the purpose of binding an immutable Java field with an immutable type to an updateable column?
Assuming we go with the mutability/updateability split, then an example of the mapping you are asking about would be what I posted above:
@Immutable
String name;
We have an immutable type (String). Here, the @Immutable is unnecessary - it's already immutable. And it is updateable. I mean that's perfectly valid, fairly normal mapping right?
Hibernate ORM will never update it, right?
And no, the column here does get updated; it is not mapped as non-updateable. That would look like either of these:
@Column(updatable=false)
String name;
or
@Updateable(false)
String name;
Beta Was this translation helpful? Give feedback.
All reactions
-
it's already immutable. And it is updateable. I mean that's perfectly valid, fairly normal mapping right?
I'm sorry but no, I don't think that makes much sense. And I still don't see the purpose of that.
To me either a property is (deeply) immutable and then there's no reason for it to be updateable, or it's mutable at least at some level, and then yes, sure, it makes sense for it to be updateable.
But deeply immutable (hence never updated) and updateable... No, I don't understand how that's useful.
Beta Was this translation helpful? Give feedback.
All reactions
-
Well I think you are still missing the point of mutability. Maybe I'm wrong though.
@Entity class TheEntity {
...
String name;
Date start;
@Immutable
Date end;
}
name is immutable - we cannot change it's internal state. But obviously nothing stops us from changing the attribute:
TheEntity theEntity = ...;
theEntity.setName( "the new name" );
This clearly triggers an update (its updateable).
start, as a Date is mutable - therefore there are 2 ways to trigger dirtiness:
TheEntity theEntity = ...;
theEntity.setStart( ... );
and
TheEntity theEntity = ...;
theEntity.getStart().setTime( ... );
As stated numerous times, because it is mutable we have to use .equals to check dirtiness and we have to create a deep copy when creating the TheEntity state array and when interacting with L2 cache.
end, as as Date, is normally mutable. However, here we have explicitly told Hibernate to treat it as immutable. This has a few impacts centered on the fact the we will ignore any internal state change. So while this dirties the entity:
TheEntity theEntity = ...;
theEntity.setEnd( ... );
this does not:
TheEntity theEntity = ...;
theEntity.getEnd().setTime( ... );
The important implication really is that we can skip the deep copies, generally speaking.
But deeply immutable (hence never updated) and updateable... No, I don't understand how that's useful.
Right, because (I think) you keep confusing mutable and updateable.
Beta Was this translation helpful? Give feedback.
All reactions
-
name is immutable - we cannot change it's internal state. But obviously nothing stops us from changing the attribute:
Ok, that's probably the source of the misunderstanding.
Then my objection would be that the meaning you assign to @Immutable is confusing, at least to me. I'd expect this:
@Immutable
Date end;
... to mean that the end field won't be mutated (but the value it holds still can, that's unrelated). Because, well, I just annotated a field with @Immutable, so that field is not mutable, right?
With that meaning, a similar field of type String could be considered non-updateable automatically.
Now I guess if your @Immutable annotation is annotated with something like @java.lang.annotation.Target({ElementType.TYPE_USE}), usage and meaning would be consistent: in the example above, the annotation would apply to Date, not to Date end. But that's still confusing as hell; probably even more so.
"Do you have anything constructive to suggest instead of wasting my time", you'll say, and you'll be right.
I suppose @MutabilityOverride would make sense in this context, and, while verbose, would at least suggest a similar behavior to @AttributeOverride/@AssociationOverride which affects the type of the attribute rather than the attribute itself. And (at least in the example above) there's no way to interpret this as affecting the field itself, since where would be the override then?
Beta Was this translation helpful? Give feedback.
All reactions
-
I suppose @MutabilityOverride would make sense in this context
We already have @Mutability to allow users to plug in custom "mutability overrides".
Then my objection would be that the meaning you assign to @immutable is confusing
I don't think it really is though. So long as the meaning is consistent regardless of the placement, that is an easy thing to communicate - @Immutable == "immutable internal state". And it is also consistent with @Mutability.
Also, the meaning you ascribe to @Immutable is just not at all what it has meant for its entire existence. 6.2 added the ability to apply @Immutable on attributes whereas previously that was not allowed. As part of that work, when @Immutable is used on a @Basic attribute, it also added this additional non-updateable semantic. In my opinion, that introduced an inconsistency in the message - the meaning of "immutable" was no longer the same depending on where it was used.
Beta Was this translation helpful? Give feedback.
All reactions
-
The conclusion after discussions with others on the team is to drop the implication that a @Immutable is non-updateable. This is consistent with what was called "clean room" option.
Does it make sense to provide an @Updateable annotation? I think for the time being I'll skip that part, or at least it is a different discussion.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
Yeah, I'm fine with the concept of a @Final annotation as a shorthand for @Column(updatable = false).
Beta Was this translation helpful? Give feedback.
All reactions
-
If we don't update a column, then mutability is irrelevant to Hibernate. We will never copy a value or compare values for dirtiness.
OK, so you're arguing that @Updatable(false) or @Final imply @Mutability(Immutability.class). Perfect! You agree with my point!
Beta Was this translation helpful? Give feedback.
All reactions
-
Since we don't make use of the mutability plan when the column is not updatable, I guess that it doesn't matter what we do with the mutability plan then, but sure, forcing the immutable mutability plan works for me.
Beta Was this translation helpful? Give feedback.
All reactions
-
we don't make use of the mutability plan when the column is not updatable
I mean, that's an optimization / implementation detail I don't care about at all. What I care about is the user-visible semantics, which it seems we agree on.
Beta Was this translation helpful? Give feedback.
All reactions
-
I said from the very beginning that this is all about (1) consistency and (2) unfortunate term overload.
Great, fine. I'll just drop my PR and we can live with the inconsistency and constantly explain how this works.
BTW, @Immutable-on-attribute was never documented. Would be nice if that happened.
Beta Was this translation helpful? Give feedback.
All reactions
-
Ahah, I think I might have figured out a major reason for us talking past each other on this stuff. Currently, updatable=false triggers some hardcoded behavior that is, if I’m not mistaken, equivalent to ImmutableMutabilityPlan, namely: no need to take a snapshot or dirty-check. (It probably still copies when putting stuff in the second-level cache though.)
And so, at least from the point of view of the user, non-updatability always implies immutability, even though there’s no explicit MutabilityPlan.
The funny thing is that after much discussion on Zulip, this was essentially the semantic we converged on. Without realizing, it seems to me, that this is the semantic we already have.
Beta Was this translation helpful? Give feedback.
All reactions
-
For example, Steve didn’t like that my impl of @Immutable on fields didn’t do anything about mutability plans. But of course it didn’t need to because snapshotting/dirty-checking are already implicitly bypassed!
OMG this shit is so confusing!
Beta Was this translation helpful? Give feedback.
All reactions
-
I'm also doing nothing else with @Immutable. It is dead to me lol.
Beta Was this translation helpful? Give feedback.
All reactions
-
😄 1