1

I'm using Spring Boot with Spring Messaging and Gson for websocket connection and sending messages to client. Now I'm facing LazyInitializationException error after adding new field to my model and sending it to client.

Here's my model class:

@Entity
public class Vehicle {
    @Expose
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Expose
    private String name;

    //...
    //some other simple fields
    //...

    @Expose
    @ElementCollection(fetch = FetchType.LAZY)
    @CollectionTable(name = "vehicle_property_values", joinColumns = @JoinColumn(name = "vehicle_id"))
    @MapKeyColumn(name = "key")
    @Column(name="value")
    private Map<String, String> properties = new HashMap<>();
}

I'm using GsonMessageConverter to convert object to json and send to client from https://github.com/Coding/WebIDE-Backend/blob/master/src/main/java/net/coding/ide/web/message/GsonMessageConverter.java

Converter is working fine but recenlty I've added Map<String, String> properties to my Vehicle model and there's a problem. I'm getting exception:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.test.vehicles.model.Vehicle.properties, could not initialize proxy - no Session

in line 105 on the GsonMessageConverter

this.gson.toJson(payload, writer);

Here's how I send messages in my Service class:

@Service
@Transactional
class VehicleServiceImpl implements VehicleService {
    @Autowired
    VehicleRepository vehicleRepository; //JpaRepository
    @Autowired
    SimpMessagingTemplate simpMessagingTemplate;

    private final ConcurrentLinkedDeque<Vehicle> pendingVehicles = new ConcurrentLinkedDeque<>(); 

    //..some other fields and methods irrelevant in this case

    //method called from controller
    @Override
    void addPendingVehicle(Vehicle vehicle) {
        //set some variables in vehicle
        vehicle = vehicleRepository.save(vehicle);
        pendingVehicles.add(vehicle);
    }

    @Scheduled(initialDelay = 60000, fixedRate = 60000)
    @Transactional
    void sendPendingVehicles() {
        if(pendingVehicles.size() > 0) {
            simpMessagingTemplate.convertAndSend("/topic/vehicles", pendingVehicles);
            pendingVehicles.clear();
        }
    }
}

Here's my WebSocket configuration (only not empty methods):

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/queue", "/topic");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/testapp").setAllowedOrigins("*").withSockJS();
    }

    @Override
    public boolean configureMessageConverters(List<MessageConverter> converters) {
        Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
        converters.add(new GsonMessageConverter().setGson(gson));
        return true;
    }
}

I have no idea how to fix it. How to make it Transactional?

user3626048
  • 706
  • 4
  • 19
  • 52
  • How do `Vehicle` objects gets to your queue? Can you share this piece of code? – Leffchik Aug 28 '17 at 13:41
  • @Leffchik I've added some more code in my `@Service` – user3626048 Aug 29 '17 at 06:18
  • You add stuff to the queue (from transaction x) then in transaction y you try to send it. Transaction x doesn't exist anymore (and thus the corresponding entity manager) that is why you get this error. To fix, read the lazy collection before adding it to the queue or make the collection non-lazy, or if it doesn't need to be serialized ignore it from serialization `@JsonIgnore` . – M. Deinum Aug 29 '17 at 06:21
  • @M.Deinum I tried to add `vehicle.getProperties()` before `pendingVehicles.add(vehicle)` but error is the same. How to make collection non-lazy? Accually in websocket messages I don't need to send `properties` but in other cases (e.g. endpoint to get all vehicles) I need it so when I add exclusion it will work in any serialization. – user3626048 Aug 29 '17 at 06:29
  • You can simply put `@ElementCollection(fetch = FetchType.EAGER)` on the `properties` field, to make it non-lazy. It might lead to some perfomance issues though – Leffchik Aug 29 '17 at 06:32
  • @Leffchik I ended up using `ExclusionStrategy` to exclude this field in websocket serialization. – user3626048 Aug 29 '17 at 06:41

1 Answers1

0

I didn't need to properties be send in websocket messages so I created annotation

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcludeWebSocketSerialization { }

And added ExclusionStrategy to my Gson

.addSerializationExclusionStrategy(new ExclusionStrategy() {
    @Override
    public boolean shouldSkipField(FieldAttributes fieldAttributes) {
    return fieldAttributes.getAnnotation(ExcludeWebSocketSerialization.class) != null;
    }

    @Override
    public boolean shouldSkipClass(Class<?> aClass) {
    return false;
    }
})
user3626048
  • 706
  • 4
  • 19
  • 52