96

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 ?

Justin Johnson
  • 30,978
  • 7
  • 65
  • 89
glmxndr
  • 45,516
  • 29
  • 93
  • 118

9 Answers9

79

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.
slartidan
  • 20,403
  • 15
  • 83
  • 131
54

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)

Nicolas
  • 24,509
  • 5
  • 60
  • 66
  • 22
    You forgot: "and make sure that the getters return only immutable values". e.g. Collection fields – Daniel Alexiuc Aug 15 '11 at 03:36
  • What's mean field based annotation? – WelcomeTo Feb 15 '13 at 14:42
  • In JPA, you can either put annotation on fields or on getters. Further information here: http://stackoverflow.com/questions/942035/hibernate-jpa-annotating-bean-methods-vs-fields – Nicolas Feb 15 '13 at 18:12
  • 1
    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. – user447607 Sep 19 '13 at 17:59
  • 6
    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. – Jordan Silva Nov 11 '15 at 08:34
  • 5
    With that approach you may still add new entities (INSERT) – andy Dec 19 '17 at 22:40
  • @andy thats a db design issue more or less then, use readonly db user or use views instead of tables? – Kalpesh Soni May 17 '18 at 15:51
  • @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. – andy May 22 '18 at 06:52
43

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

rzymek
  • 9,064
  • 2
  • 45
  • 59
Toby Artisan
  • 1,639
  • 3
  • 23
  • 26
  • 17
    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". – David Lavender Aug 11 '15 at 15:11
  • 3
    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. – Kt Mack Sep 08 '17 at 15:07
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.

Andrew B
  • 1,618
  • 2
  • 21
  • 30
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?

Mac
  • 14,615
  • 9
  • 62
  • 80
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.

Timmo
  • 3,142
  • 4
  • 26
  • 43
12

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

rzymek
  • 9,064
  • 2
  • 45
  • 59
Pau
  • 803
  • 1
  • 6
  • 12
9

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

Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • If you declare a compiler error for all access to set methods, why do you declare them? – Nicolas Feb 25 '11 at 09:41
  • 2
    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 aspect – Sean Patrick Floyd Feb 25 '11 at 09:43
  • So... a client that uses entities through a Jar can access setters? – Nicolas Feb 25 '11 at 09:51
  • @Nicolas 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. – Sean Patrick Floyd Feb 25 '11 at 09:57
  • 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. – glmxndr Feb 25 '11 at 14:04
  • why downvote? this is quite interesting solution IMHO – vach Mar 18 '14 at 11:50
  • @Vach I agree, but I've been downvoted for suggesting aspectj before – Sean Patrick Floyd Mar 18 '14 at 12:44
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.

JavaMan
  • 61
  • 1
  • 2