Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java JPA Preventing Proxies from calling db

I have a spring boot (1.5.4.RELEASE) project using Java 8. I have an entity and it's related domain class like this:

@Entity
@Table(name = "Foo", schema = "dbo")
public class FooEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "Id")
    private int id;

    @Column(name="Name")
    private String name;

    @Column(name="Type")
    private String type;

    @Column(name="Color")
    private String color;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "Car")
    private Car car;

    //getter and setter
}

public class Foo {
    private int id;
    private String name;
    private String type;
    private String color;
    private Car car;

    //Constructors and getters
}

I want to create a repository that fetches this Foo object from the DB but only fetching the complex fields if the user asks for them to prevent unnecessary join statements. The repo looks like this:

import static com.test.entities.QFooEntity.fooEntity;
import static com.test.entities.QCarEntity.carEntity;

@Repository
public class FooRepository {
    private final JPAQuery<FooEntity> query = createQuery().from(fooEntity);

    public FooRepository getFooByName(String name) {
        query.where(fooEntity.name.eq(name));
        return this;
    }

    public FooRepository withCar() {
        query.leftJoin(fooEntity.car, carEntity).fetchJoin();
        return this;
    }

    public Foo fetch() {
        FooEntity entity = query.fetchOne();
        return FooMapper.mapEntityToDomain().apply(entity);
    }
}

So a barebones call for a Foo object will return the Entity with values for all the fields except for the car field. If the user wants car information then they have to explicitly call withCar.

Here is the mapper:

public class FooMapper {
    public static Function<FooEntity, Foo> mapEntityToDomain() {
        return entity -> { 
            return new Foo(e.getId(), e.getName(), e.getType(), e.getColor(), e.getCar());
        };
    }
}

The problem is when you do e.getCar() if the value is not there (i.e. there's a proxy present) JPA will go out and fetch it for you. I don't want this to be the case. It will just grab the values and map them to the domain equivalent if it's not there then null.

One solution that I've heard (and tried) is calling em.detach(entity); however, this doesn't work as I intended because it throws an exception when you try to access getCar and I've also heard this is not best practice.

So my question is what is the best way to create a repo using a builder pattern on a JPA entity and not have it call the DB when trying to map.

like image 481
Richard Avatar asked Sep 22 '17 20:09

Richard


People also ask

Can we use JPA with R2DBC?

JPA cannot deal with reactive repositories such as provided by Spring Data R2DBC. This means you will have to do more things manually when using R2DBC. There are other reactive drivers around such as for example Quarkus Reactive Postgres client (which uses Vert.

What is JPA proxy?

The JPA lazy loading mechanism can either be implemented using Proxies or Bytecode Enhancement so that calls to lazy associations can be intercepted and relationships initialized prior to returning the result back to the caller.

What is JPA Baeldung?

The Java Persistence API (JPA) is a specification that defines how to persist data in Java applications. The primary focus of JPA is the ORM layer. Hibernate is one of the most popular Java ORM frameworks in use today.


2 Answers

You could create a utility method that will return null if the given object is a proxy and is not initialized:

public static <T> T nullIfNotInitialized(T entity) {
    return Hibernate.isInitialized(entity) ? entity : null;
}

Then you can call the method wherever you need it:

return new Foo(e.getId(), e.getName(), e.getType(), e.getColor(), nullIfNotInitialized(e.getCar()));
like image 199
Dragan Bozanovic Avatar answered Oct 02 '22 07:10

Dragan Bozanovic


Just map it to a new object and leave out the Car relation, this is the standard approach. You can use MapStruct and just ignore the car field during mapping: http://mapstruct.org/documentation/stable/reference/html/#inverse-mappings

like image 33
highstakes Avatar answered Oct 02 '22 06:10

highstakes