Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Mutability consistency #6090

sebersole started this conversation in Design Proposals
Feb 7, 2023 · 4 comments · 25 replies
Discussion options

Hibernate has different notions of (im)mutability:

  1. Whether the internal state of a value can change. E.g. a Java Date is mutable because one can change its internal state via Date#setTime. A String, 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.
  2. 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 -

  1. In a clean-room, I think what makes the most sense is to consider @Immutable a shorthand for an immutable MutabilityPlan. For the notion of updateability I would have either relied on @Column or introduced a new annotation (@Updateable?) specifically for case (2). Keeping in mind that allowing @Immutable on attributes is not in a Final release, there is no backwards compatibility concern here. I also like that the names actually reflect the intent.
  2. An option we discussed on https://hibernate.zulipchat.com/#narrow/stream/132094-hibernate-orm-dev/topic/.40Immutable.20and.20AttributeConverter[Zulip] was to consider @Immutable as 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:

  • @Immutable would 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.
  • @Mutability would 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:

  • @Immutable is allowed and is shorthand for @Mutability(Immutability.class) plus Column(..., updatable=false) on all columns associated with the attribute.
  • @Mutability is allowed, supplying a custom MutabilityPlan

With the clean room option:

  • @Immutable is allowed and only affects mutability - shorthand for @Mutability(Immutability.class)
  • @Mutability is allowed, supplying a custom MutabilityPlan
  • @Updateable(false) is allowed and is shorthand for Column(..., updatable=false) on all columns associated with the attribute

With the Zulip option:

  • @Immutable is allowed and is shorthand for @Mutability(Immutability.class) plus Column(..., 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.
  • @Mutability is allowed, supplying a custom MutabilityPlan
  • 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:

  • @Immutable is allowed on the converter class and is shorthand for @Mutability(Immutability.class)
  • @Mutability is allowed, supplying a custom MutabilityPlan that is applied to attributes the converter is applied to
  • @Updateable is not allowed

With Zulip option:

  • @Immutable is not allowed on the converter class.
  • @Mutability is allowed, supplying a custom MutabilityPlan that is applied to attributes the converter is applied to

UserType

Same as AttributeConverter

You must be logged in to vote

Replies: 4 comments 25 replies

Comment options

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#embeddable
  • MyEmbeddable#myDate
  • Date (or, if there is a UserType/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?

You must be logged in to vote
11 replies
Comment options

sebersole Feb 8, 2023
Maintainer Author

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;
Comment options

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.

Comment options

sebersole Feb 8, 2023
Maintainer Author

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.

Comment options

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?

Comment options

sebersole Feb 8, 2023
Maintainer Author

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.

Comment options

sebersole
Feb 8, 2023
Maintainer Author

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.

You must be logged in to vote
13 replies
Comment options

Yeah, I'm fine with the concept of a @Final annotation as a shorthand for @Column(updatable = false).

Comment options

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!

Comment options

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.

Comment options

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.

Comment options

sebersole Feb 9, 2023
Maintainer Author

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.

Comment options

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.

You must be logged in to vote
1 reply
Comment options

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!

Comment options

sebersole
Feb 9, 2023
Maintainer Author

I'm also doing nothing else with @Immutable. It is dead to me lol.

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

AltStyle によって変換されたページ (->オリジナル) /