shalama
shalama

Reputation: 1335

JPA Spring ignores Lazy loading inside @Transacational

I have a spring service class where I'm loading a JPA object (target) via CRUD. This target class has a one-to-may mapping that is set to lazy loading.

I would like to query this object inside a spring service method that is annotated with @Transactional and avoid that the childs are being loaded.

When I execute the following code all child data is loaded and laziness is ignored.

        @Override
        @Transactional
        public boolean changeState(boolean enabled, final EventType eventType, final String deviceSerialNumber) {
            final UniqueUser user = userService.getUser();
            final Target target = targetRepository.findEventsByUserIdAndTenantIdAndTargetDeviceId(user.getUserId(), user.getTenantId(), deviceSerialNumber);
//here everything gets loaded
            if (target == null) {
                return false;
            }
            final List<EventHistory> events = target.getEvents().stream()
                    .filter(event -> event.getEventType() == eventType)
                    .filter(event -> event.isActive() != enabled)
                    .collect(Collectors.toList());
            events.forEach(event -> event.setActive(enabled));
            if (events.isEmpty()) {
                return false;
            }
            return true;
        }

Mappings:

    @ToString
    @Entity
    @Table(name = "target")
    public class Target {

        @Id
        @GeneratedValue(generator = "uuid")
        @GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
        @Column(name = "id", unique = true)
        private UUID id;

        @Column(name = "user_id")
        private String userId;

        @Column(name = "tenant_id")
        private String tenantId;

        @Column(name = "target_device_id")
        private String targetDeviceId;

        @Column(name = "target_type")
        private TargetType targetType;

        @OneToMany(mappedBy = "target", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
        @JsonManagedReference
        private List<EventHistory> events = new ArrayList<>();

        public void addEvents(EventHistory event) {
            events.add(event);
            event.setTarget(this);
        }

    }

    @Entity
    @Data
    @Table(name = "event_history")
    public class EventHistory {

        @Id
        @GeneratedValue(generator = "uuid")
        @GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
        @Column(name = "id", unique = true)
        private UUID id;

        @Column(name = "active")
        private boolean active;


        @OneToMany(mappedBy = "event", cascade = CascadeType.ALL, orphanRemoval = true)
        @JsonManagedReference
        private List<EventTimestamp> timestamps = new ArrayList<>();

        @ManyToOne(optional = false, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
        @JoinColumn(name = "target_id", nullable = false)
        @OnDelete(action = OnDeleteAction.CASCADE)
        @JsonBackReference
        private Target target;

        public void addTimestamps(EventTimestamp eventTimestamp) {
            timestamps.add(eventTimestamp);
            eventTimestamp.setEvent(this);
        }

    }

    @Entity
    @Data
    @Table(name = "event_timestamp")
    public class EventTimestamp {
        @Id
        @GeneratedValue(generator = "uuid")
        @GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
        @Column(name = "id", unique = true)
        private UUID id;


        @Column(name = "due_timestamp")
        private Timestamp dueTimestamp;

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

        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "event_id", nullable = false)
        @JsonBackReference
        private EventHistory event;

So my question is how to keep lazy loading inside transaction annotated functions?

Upvotes: 0

Views: 969

Answers (2)

shalama
shalama

Reputation: 1335

My first assumption that the root cause had been the wrongly implemented repository function was wrong. The real issue was the @ToString annotation. This added the one-to-many event collection to toString(). While being inside the transaction and accessing the object, toString got invoked and the the collection was loaded.

Solution was to exclude the collection from toString via.

@OneToMany(mappedBy = "target", cascade = CascadeType.ALL, orphanRemoval = true)
@ToString.Exclude
private List<EventHistory> events;

Upvotes: 2

shalama
shalama

Reputation: 1335

I figured it out. The problem was ins the Repository code. The findBy method is expecting a List instead of a single object. My original repository looked like this

@Repository
public interface TargetRepository extends CrudRepository<Target, UUID> {

  Target findEventsByUserIdAndTenantIdAndTargetDeviceId(String userId, String tenantId, String targetId);

}

Changing it to the below version fixed it.

@Repository
public interface TargetRepository extends CrudRepository<Target, UUID> {
    List<Target> findEventsByUserIdAndTenantIdAndTargetDeviceId(String userId, String tenantId, String targetId);
}

Upvotes: 0

Related Questions