Search code examples
javaspringspring-sessionspring-messaging

LazyInitializationException with Spring Messaging and GsonMessageConverter


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?


Solution

  • 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;
        }
    })