Tagbangers Blog

Rest API を作っていて LazyInitializationException が出た時の話

最近、SPAの開発がありました。
サーバサイドは当社のスタンダード、Spring + Hibernate で開発しているのですが、題名の通り LazyInitializationException にぶち当たりまして、その対応をした時の話です。

Rest API になるので、例えばコントローラはこんな感じです。
レスポンスは jackson-databind で jsonシリアライズされるようにしています。

@RestController
@RequestMapping("/companies")
public class CompanyController {

    @Inject
    private CompanyService companyService;

    @GetMapping
    public List<Company> list() {
        return companyService.findCompanies();
    }
}

Companyリストをそのまま返却していますが、このようにHibernateのEntityをそのまま返すとEntityクラスやServiceクラスの設計によっては、jsonシリアライズ時に LazyInitializationException が発生します。

例えばこういう時です。

  • Company が 別のEntity(Employee)を FetchType.LAZY で参照している
  • Companyリストを取得する時に(ここでは findCompanies)、Employeeをfetchしていない

うーむ、レスポンス用に別のPOJOを用意してEntityの値を詰め直さないといけないのかな・・・と、思いましたがこれが使えました!

Hibernate5Module.Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS

Controllerに直接に書いてみるとこんな感じです。
(fetchしていないオブジェクトはnullとしてレスポンスされるようになります)

@RestController
@RequestMapping("/companies")
public class CompanyController {

    @Inject
    private CompanyService companyService;

    @GetMapping
    public String list() {
        ObjectMapper objectMapper = new ObjectMapper();
        Hibernate5Module module = new Hibernate5Module();
        module.enable(Hibernate5Module.Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS);
        objectMapper.registerModule(module);
        
        List<Company> companies = companyService.findCompanies();
        return objectMapper.writeValueAsString(companies);
    }
}

ただ、Controllerに毎度書くのはイケてないので、Jackson2ObjectMapperBuilder の Bean を設定してあげれば(Spring Boot の場合)、冒頭のコードのままで LazyInitializationException を回避できました。

@Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
   Hibernate5Module module = new Hibernate5Module();
   module.enable(Hibernate5Module.Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS);
   Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder().modules(module);
   return builder;
}