5

I have an @Entity with 20 fields including the index and a timestamp updated by Hibernate:

@Entity
public class MyEntity {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  @UpdateTimestamp
  private LocalDateTime updatedTime;
  private String ....
  private String ....

I have a default constructor for Hibernate and a secondary constructor to set everything but the id and updatedTime.

I don't need (or want) setters for id or updatedTime because I only want Hibernate to set them, and it does that with reflection.

I wanted to try out Lombok to see if I could avoid a lot of boilerplate involved here but @Data adds both getters and setters and doesn't create the same constructors.

I'm also concerned that Lomboks generated equals/hashCode and toString methods can cause subtle problems with Hibernate.

This will mean I will have to use a combination of the other Lombok annotations to do this.

How do I safely create an Entity using Lombok like this?
Am I going to have to use a mixture of annotations and manual methods?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
opticyclic
  • 7,412
  • 12
  • 81
  • 155

2 Answers2

5

Some lombok annotations like @EqualsAndHashCode and @ToString have Exclude option. But neither @Data nor @AllArgsConstructor has a similar option. But @Data generates setters for all fields for which a setter is not already defined. So you would define a setter as below for the required fields, which does nothing.

private void setId(Long id) {
    // Do nothing
}

Instead of the @AllArgsConstructor, you could either use @RequiredArgsConstructor, but annotate all the fields to be in the constructor with @NonNull (or the field should be final). Refer this answer for RequiredArgsConstructor.

My suggested approach : Another way would be to use @Builder annotation along with @AllArgsConstructor(access = AccessLevel.PRIVATE). (NOTE : Builder by default adds a private all argument constructor, but this is done only if there are no other constructors. But in your case, a default constructor exists and you need to explicitly mention the all args annotation.)

This would prevent the use of the constructor from outside, but at the same time allow you to create objects using the builder. At this point, you could set the values to id and updateTime using the builder. To prevent this you need to add the below code as well.

public static class MyEntityBuilder {
    // access is restricted using
    // these private dummy methods.

    private MyEntityBuilder id(Long id) { 
        return this; 
    }

    private MyEntityBuilder updateTime(LocalDateTime time) { 
        return this; 
    }

}

So, even though it is not possible to achieve your requirement directly, you could do so by adding two dummy setter methods and another two dummy methods within the builder class.

Gautham M
  • 4,816
  • 3
  • 15
  • 37
  • https://stackoverflow.com/a/23778200/7804477 -> SomeArgsContructor is in development as well. – Gautham M May 05 '21 at 13:02
  • Better yet, just write out a constructor that takes in only the properties and add @Builder to that. This way the generated values cannot be changed by the builder methods. – Nate T Jun 06 '21 at 06:49
  • @NathanToulbert OP has mentioned that such a constructor already exists. Also the question states "***... I wanted to try out Lombok to see if I could avoid a lot of "boilerplate" involved here***. The entity has 20 fields, so the secondary constructor would have 18 parameters. And that's a very longgg constructor :-)). If you could avoid the lengthy constructor(it would exists but you don't see it) using two small methods, then that would be better when the purpose is to reduce the boilerplate code. – Gautham M Jun 06 '21 at 08:22
  • Agreed. I read this question last night, but forgot about the twenty properties. That said, in this case, I wouldve just generated the constructor via IDE and edited, Seems like finding the workaround is the least efficient solution here. – Nate T Jun 06 '21 at 08:39
  • The main issue W/ the AllArgsConstructor / class level Builder is that it is generating a method to set properties that should be immutable. It would be nice if Lombok would add a complimentary method to NonNull such as Exclude so that properties could be explicitly left out of these. – Nate T Jun 06 '21 at 08:52
  • @NathanToulbert yes it would be good if SomeArgs with Exclude option is released. But generating the constructor using IDE is same as generating getters/setters via IDE. That would save time but not reduce boilerplate code. Seeing a long constructor might be confusing for a new developer on what fields are included and what are excluded especially for a class with many fields. I would definitely suggest your approach when there are less fields to include than exclude. But when there are very less fields to exclude, and you need to reduce the boilerplate code, the workaround would be better. – Gautham M Jun 06 '21 at 09:08
  • @NathanToulbert Anyway it is more of a personal choice depending on the developer implementing it rather than a defined rule – Gautham M Jun 06 '21 at 09:12
  • You think that it is acceptable to provide end users a method that overwrites the generated id field, and which will almost certainly make the object that it was called on useless, in order to reduce boilerplate code? That would be a bug in any production environment. Clean code is important, but not more so than reliable code. – Nate T Jun 06 '21 at 09:16
  • @NathanToulbert I am not clear on what you meant by .."generating a method to set properties that should be immutable". Actually the generated method is a private one and won't be accessible from outside. – Gautham M Jun 06 '21 at 09:16
  • Unless you explicitly set it, it is public, I believe. – Nate T Jun 06 '21 at 09:18
  • Were going to fill this page before were finished... : ) – Nate T Jun 06 '21 at 09:20
  • @NathanToulbert ofcourse. The methods as part of the builder class i have added are private and the comment explains that. – Gautham M Jun 06 '21 at 09:21
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/233374/discussion-between-gautham-m-and-nathan-toulbert). – Gautham M Jun 06 '21 at 09:22
1

we have @NoArgsConstructor @AllArgsConstructor for generating constructor with lombok.

this is how i create them.


@Entity
@Table(schema = "S25", name = "bank")
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Getter
@Setter
public class Bank {

  @Id
  @SequenceGenerator(name = "bankEntitySeq", sequenceName = "SEQ_BANKS", allocationSize = 1)
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "bankSeq")
  @Column(name = "bank_id")
  private Long bankId;

  @Column(name = "bank_name")
  private String bankName;

  
  @Column(name = "created_on")
  private Date createdOn =
      new Date(); //Date.from(Instant.now().atZone(ZoneId.of("UTC")).toInstant());
}
Dharman
  • 30,962
  • 25
  • 85
  • 135
saij9999
  • 282
  • 1
  • 3
  • 18