Home > OS >  Lombok - expose @EmbeddedId fields to @SuperBuilder
Lombok - expose @EmbeddedId fields to @SuperBuilder

Time:10-30

is there a way to expose properties defined in @EmbeddedId to @SuperBuilder?

// Composite id class
@Data
@Builder(builderMethodName = "having")
@NoArgsConstructor
@AllArgsConstructor
@Embeddable
public class TheId {
    @Column(name = "name", insertable = false)
    private String name;

    @Column(name = "description", insertable = false)
    private String description;
}

// Base entity class
@SuperBuilder 
@Getter
@Immutable
@MappedSuperclass
@AllArgsConstructor
@NoArgsConstructor
public class BaseEntity implements Serializable {
    @Delegate
    @EmbeddedId
    private TheId id;

    @Column(name = "long_description", insertable = false)
    private String longDescription;
}

// Concreate entity over database view
@Entity
@Table(name = "view_number_1")
@Getter
@Setter
@Immutable
@AllArgsConstructor(staticName = "of")
@SuperBuilder(builderMethodName = "having")
public class ConcreteEntity1 extends BaseEntity {}

I'd like to be able to write code like that:

ConcreateEntity1.having()
    .name("the name")
    .description("something")
    .longDescription("akjsbdkasbd")
    .build();

instead of

ConcreateEntity1.having()
    .id(TheId.having()
        .name("the name")
        .description("something")
        .build())
    .longDescription("akjsbdkasbd")
    .build();

Reason behinds whole concept: same-named columns are present across several views so it's kind of logical to have one base class for them all. Although entities themselves are immutable (db view based) I'd like using builder in tests that's why I'd like to have them as above.

CodePudding user response:

Since you have used @Delegate annotation in id then you should be able to set/get TheId fields directly from BaseEntity Class.

You can read here more about it.

CodePudding user response:

There is no automatic way to insert delegates into a @(Super)Builder. However, if your delegatee class (TheId in this case) does not contain too many fields, you can manually add the corresponding setter methods to the builder class. Just add the correct builder class header, put your code in it, and Lombok will add all the remaining code that you do not manually write.

Admittedly, it's a bit tricky. The code @SuperBuilder generates is quite complex, and you do not want to add too much manual stuff, because you want to keep the advantages of Lombok: If you change something in your annotated class, you wouldn't want to rewrite all your manual methods. So this solution tries to keep most of the automation at the cost of a little bit of performance/memory waste.

@SuperBuilder(builderMethodName = "having")
public class BaseEntity {
    @Delegate
    private TheId id;

    private String longDescription;

    public static abstract class BaseEntityBuilder<C extends BaseEntity, B extends BaseEntityBuilder<C, B>> {
        private TheId.TheIdBuilder theIdBuilder = TheId.having();
        
        // Manually implement all delegations.
        public B name(String name) {
            theIdBuilder.name(name);
            // Instantiating every time you call the setter is not optimal, 
            // but better than manually writing the constructor.
            id = theIdBuilder.build();
            return self();
        }
        public B description(String description) {
            theIdBuilder.description(description);
            id = theIdBuilder.build();
            return self();
        }
        
        // Make this method invisible. 
        @SuppressWarnings("unused")
        private B id() {
            return self();
        }
    }
}

Is it worth it? That's a matter of taste and how much it improves the applicability/usability/readability of the builder in your case.

  • Related