28

I read that it's useful to use builder pattern when you have a class with a lot of parameters. I wonder how you can implement an entity using builder pattern. It would be great if you can provide sample code.

sa_vedem
  • 757
  • 1
  • 7
  • 16
  • 3
    Why does it matter that the class is an entity? Why is using the builder pattern to build entities any different to using it to build anything else? – Tom Anderson Nov 13 '11 at 12:34
  • I want it to be an entity, in order to be able to store it in db. – sa_vedem Nov 13 '11 at 13:41
  • Like @TomAnderson said, you do not need differ between POJO class and entity class builder. Just take a look at [here](https://medium.com/@ajinkyabadve/builder-design-patterns-in-java-1ffb12648850) for example and use general implementation of the builder pattern. – Peter S. Oct 03 '19 at 09:36
  • i would never use a builder to create an instance of an entity, unless it is for unit tests. If you use builder pattern then default values set on some fields on your entity would not be set. Instantiate entities with new keyword, so that business logic on the domain object is executed. If you use builder pattern, and do not set field "a" where field a has default value "5" (private Integer a=5;) then field "a" will have value null. I would never put the [@Builder annotation](https://www.projectlombok.org/features/Builder) on an [@Entity](https://www.objectdb.com/api/java/jpa/Entity) – Krakowski mudrac May 05 '21 at 14:22

1 Answers1

21

Of course it is possible, you just have to provide a (possibly nested) Builder for every Entity.

Here is a working example:

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class FluentEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String someName;
    private int someNumber;
    private boolean someFlag;

    protected FluentEntity(){}

    private FluentEntity(String someName, int someNumber, boolean someFlag) {
        this.someName = someName;
        this.someNumber = someNumber;
        this.someFlag = someFlag;
    }

    public long getId() {
        return id;
    }

    public String getSomeName() {
        return someName;
    }

    public int getSomeNumber() {
        return someNumber;
    }

    public boolean isSomeFlag() {
        return someFlag;
    }

    public static FluentEntityBuilder builder() {
        return new FluentEntityBuilder();
    }

    public static class FluentEntityBuilder {

        private String someName;
        private int someNumber;
        private boolean someFlag;

        public FluentEntityBuilder setSomeName(final String someName) {
            this.someName = someName;
            return this;
        }

        public FluentEntityBuilder setSomeNumber(final int someNumber) {
            this.someNumber = someNumber;
            return this;
        }

        public FluentEntityBuilder setSomeFlag(final boolean someFlag) {
            this.someFlag = someFlag;
            return this;
        }

        public FluentEntity build() {
            return new FluentEntity(someName, someNumber, someFlag);
        }

    }

}

The code to use it would be this:

FluentEntity entity = FluentEntity.builder().setSomeName(someName).setSomeNumber(someNumber)
                .setSomeFlag(someFlag).build();

Just keep in mind that you have to exclude auto-generated fields like the primary key (in this example id) if you have some.

If you want to get rid of the "boilerplate" code for creating Builder classes for every Entity I would recommend a convenience library, something like Lombok. Then you will get your Builders (and even more) by just annotating your Entites, maybe it costs a little extra work to exclude the id fields.

You should take a look at Project Lombok

Nevertheless, here is some code to test this Builder (implemented with Spring Boot and Hibernate).

The repository:

import org.springframework.data.repository.CrudRepository;

import com.example.model.FluentEntity;

public interface FluentEntityRepository extends CrudRepository<FluentEntity, Long> {

}

And here are some tests:

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;

import java.util.stream.StreamSupport;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

import com.example.model.FluentEntity;

@RunWith(SpringRunner.class)
@Transactional
@SpringBootTest
public class FluentEntityRepositoryTests {

    @Autowired
    private FluentEntityRepository fluentEntityRepository;

    @Test
    public void insertAndReceiveFluentEntityCreatedWithBuilder() {
        final String someName = "name";
        final int someNumber = 1;
        final boolean someFlag = true;

        FluentEntity entity = FluentEntity.builder().setSomeName(someName).setSomeNumber(someNumber)
                .setSomeFlag(someFlag).build();

        entity = fluentEntityRepository.save(entity);
        assertThat("Entity did not get a generated Id!", entity.getId(), greaterThan(-1L));
        assertThat("Entity name did not match!", entity.getSomeName(), is(someName));
        assertThat("Entity number did not match!", entity.getSomeNumber(), is(someNumber));
        assertThat("Entity flag did not match!", entity.isSomeFlag(), is(someFlag));
    }

    @Test
    public void insertSomeAndReceiveFirst() {
        fluentEntityRepository.save(FluentEntity.builder().setSomeName("A").setSomeNumber(1).setSomeFlag(true).build());
        fluentEntityRepository
                .save(FluentEntity.builder().setSomeName("B").setSomeNumber(2).setSomeFlag(false).build());
        fluentEntityRepository.save(FluentEntity.builder().setSomeName("C").setSomeNumber(3).setSomeFlag(true).build());

        final Iterable<FluentEntity> findAll = fluentEntityRepository.findAll();
        assertThat("Should get some iterable!", findAll, notNullValue());

        final FluentEntity fluentEntity = StreamSupport.stream(findAll.spliterator(), false).findFirst().get();
        assertThat("Should get some entity!", fluentEntity, notNullValue());
    }

}
Aron Hoogeveen
  • 437
  • 5
  • 16
Kevin Peters
  • 3,314
  • 1
  • 17
  • 38
  • Will the JPA framework be able to "automatically" (`@Autowire`?) create instances of Entities that have setters only in the builder? – Sridhar Sarnobat Mar 06 '18 at 20:13
  • 1
    I don't know if I got your question, but in general the setters are not needed if field access is used. Then the JPA provider does not invoke the setters and the builders may be sufficient for your business code. See "2.2 Persistent Fields and Properties" in: http://download.oracle.com/otn-pub/jcp/persistence-2_1-fr-eval-spec/JavaPersistence.pdf – Kevin Peters Mar 08 '18 at 19:48
  • Ok, but the main reason I want to use a builder is so that I can make fields `final`. If you use field access, you can't make them final can you? – Sridhar Sarnobat Mar 08 '18 at 19:50
  • 1
    I guess the spec says no. 2.1 The Entity Class: "No methods or persistent instance variables of the entity class may be final." – Kevin Peters Mar 08 '18 at 19:53
  • Thanks. I guess people like me should stick to constructor injection then (though I don't like the provider pattern). – Sridhar Sarnobat Mar 08 '18 at 19:55
  • 1
    You're welcome. If we talk about something like a DI framework like Spring you're totally right to go with constructor injection. I think that's the terms origin. In your Entity case (JPA) the fields are internally either filled via field or property (setters). But you're still right, creating constructors for your usecases is convenient. You only have to ensure to keep the default (parameterless) constructor, either in public or protected scope. – Kevin Peters Mar 08 '18 at 20:16
  • I am [wondering](https://stackoverflow.com/questions/76395723/how-do-you-ensure-type-safety-when-instantiating-object-via-jpa-queries) whether you can use a builder in JPA queries. If not, what about type-safety? – Sergey Zolotarev Jun 03 '23 at 11:00