How to make an Entity read-only?
Asked Answered
T

9

99

What is the proper way to make an Entity read-only with JPA ? I wish my database table to never be modified at all programmatically.

I think I understand that I should lock my objects with LockModeType.READ. Is it possible to use an annotation to make my entities directly locked after retrieval from the database ? Or do I have to mess around and override my generic DAO for that specific entity ?

Theater answered 25/2, 2011 at 9:23 Comment(0)
E
56

A solution is to use field based annotation, to declare your fields as protected and to propose only public getter. Doing so, your objects can not be altered.

(This solution is not entity specific, it is just a way to build immutable objects)

Erdah answered 25/2, 2011 at 9:31 Comment(8)
You forgot: "and make sure that the getters return only immutable values". e.g. Collection fieldsCondon
What's mean field based annotation?Kokanee
In JPA, you can either put annotation on fields or on getters. Further information here: #942535Erdah
It works but I hate it. Makes testing unnecessarily hard as you have to come up with something to break encapsulation. I propose a custom annotation which causes an exception if a setter method is called and no testing framework is detected. Said annotation would also look like @Deprecated in your IDE. Alternatively you could write an annotation as an extension of your favorite testing framework.Atelier
You should use private instead protected. Making it protected you can still access the property by other classes in the same package. Rule of thumbs instances variables are always private.Brio
With that approach you may still add new entities (INSERT)Fieldstone
@Fieldstone thats a db design issue more or less then, use readonly db user or use views instead of tables?Capreolate
@KalpeshSoni I mean you could create an entity and persist it (with no values set at all). If no not null constraint is present on any column, the new entity may be inserted.Fieldstone
O
82

In your entity add an EntityListener like this:

@Entity
@EntityListeners(PreventAnyUpdate.class)
public class YourEntity {
    // ...
}

Implement your EntityListener, to throw an exception if any update occurs:

public class PreventAnyUpdate {

    @PrePersist
    void onPrePersist(Object o) {
        throw new IllegalStateException("JPA is trying to persist an entity of type " + (o == null ? "null" : o.getClass()));
    }

    @PreUpdate
    void onPreUpdate(Object o) {
        throw new IllegalStateException("JPA is trying to update an entity of type " + (o == null ? "null" : o.getClass()));
    }

    @PreRemove
    void onPreRemove(Object o) {
        throw new IllegalStateException("JPA is trying to remove an entity of type " + (o == null ? "null" : o.getClass()));
    }
}

This will create a bullet proof safety net for your entity with JPA lifecycle listeners.

  • PRO: JPA standard - not hibernate specific
  • PRO: very safe
  • CON: only shows write attempts at runtime. If you want a compile time check, you should not implement setters.
Ohare answered 25/8, 2015 at 14:45 Comment(1)
I'm seeing this a few years later, and it's still a great solution. :)Nazarite
E
56

A solution is to use field based annotation, to declare your fields as protected and to propose only public getter. Doing so, your objects can not be altered.

(This solution is not entity specific, it is just a way to build immutable objects)

Erdah answered 25/2, 2011 at 9:31 Comment(8)
You forgot: "and make sure that the getters return only immutable values". e.g. Collection fieldsCondon
What's mean field based annotation?Kokanee
In JPA, you can either put annotation on fields or on getters. Further information here: #942535Erdah
It works but I hate it. Makes testing unnecessarily hard as you have to come up with something to break encapsulation. I propose a custom annotation which causes an exception if a setter method is called and no testing framework is detected. Said annotation would also look like @Deprecated in your IDE. Alternatively you could write an annotation as an extension of your favorite testing framework.Atelier
You should use private instead protected. Making it protected you can still access the property by other classes in the same package. Rule of thumbs instances variables are always private.Brio
With that approach you may still add new entities (INSERT)Fieldstone
@Fieldstone thats a db design issue more or less then, use readonly db user or use views instead of tables?Capreolate
@KalpeshSoni I mean you could create an entity and persist it (with no values set at all). If no not null constraint is present on any column, the new entity may be inserted.Fieldstone
F
48

Hibernate also has a org.hibernate.annotations.Immutable annotation that you can put on the type, method, or field.

Four answered 9/5, 2012 at 14:5 Comment(2)
Be careful with @Immutable. It's not very helpful. If you try to update, it won't error: it'll just silently fail. From Hibernate's documentation: "Updates to an immutable entity will be ignored, but no exception is thrown".Wildee
You can also annotate the entity with @Immutable. In our case, a silent failure was what we needed as we wanted to update certain entities but not others, which should instead be updated using stored procedures.Idolize
L
26

If your JPA implementation is hibernate - you could use the hibernate Entity annotation

@org.hibernate.annotations.Entity(mutable = false)

Obviously this will tie your model to hibernate though.

Licastro answered 12/10, 2011 at 11:11 Comment(1)
Entity is deprecated in jpa2 hibernate instead use @ImmutableRailroader
D
16

IIRC you could set every field to insertable = false and updatable = false in your @Column annotations, but I'm sure there must be a better method... :)

I don't suppose this helps?

Denney answered 25/2, 2011 at 9:26 Comment(0)
S
13

I think what you are looking for is your entity to be Immutable. Hibernate supports this; JPA(at least JPA 1.0) does not. I suppose you can only control this by providing only getters and make sure that the getters return only immutable values.

Sigma answered 25/2, 2011 at 9:40 Comment(0)
H
12

Eclipselink implementation also offers you the @ReadOnly annotation at the entity level

Hilel answered 25/2, 2011 at 13:25 Comment(1)
where is the reference i could not find i on jun 2016 nowFellowship
I
10

This is probably going to catch me a downvote because I always get downvoted for suggesting it, but you could use AspectJ in several ways to enforce this:

Either automate Mac's solution (make AspectJ inject the @Column annotation):

declare @field : (@Entity *) *.* : @Column(insertable=false);

Or declare a compiler error for all access to set methods:

declare error : execution((@Entity *) *.set*(*) );

Downside: you need to add AspectJ compilation to your build, but that's easy if you use ant or maven

Iridissa answered 25/2, 2011 at 9:39 Comment(7)
If you declare a compiler error for all access to set methods, why do you declare them?Erdah
To allow the Framework to use them The compiler error is only for local code, the JPA provider's code is usually not woven using the aspectIridissa
So... a client that uses entities through a Jar can access setters?Erdah
@Erdah Using the declare error approach, yes. This only works if the calling code is available to the AspectJ compiler. But the first approach works nevertheless, as it modifies the actual Entity class files.Iridissa
Just so you know, it's not useful for me, but I upvoted anyways. Not because you complained, but because it actually may be useful to someone else. :) Thanks for mentioning this.Theater
why downvote? this is quite interesting solution IMHOLyndel
@Vach I agree, but I've been downvoted for suggesting aspectj beforeIridissa
U
6

If you are using spring-data or are otherwise using the Repository pattern, don't include any save / update / create / insert / etc methods in the Repository for that particular entity. This can be generalized by having a base class / interface for readonly entities, and an updatable one that extends the readonly one for updatable entities. As other posters have pointed out, the setters may also be made non-public to avoid developers accidentally setting values that they are then unable to save.

Unwished answered 20/3, 2018 at 13:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.