Spring Data Worker Threads And Lazy Initialization Failure

Our environment is Spring Boot with Spring Data JPA. I’m dealing with significant legacy code, and we have a generic problem I’m trying to fix.

We have a number of places where a REST call spawns a background thread:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>org.hibernate.LazyInitializationException: could not initialize proxy [org.showpage.threadingtransactions.dbmodel.Author#1] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:165) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:314) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:44) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:102) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.showpage.threadingtransactions.dbmodel.Author$HibernateProxy$n6D0Du2E.getName(Unknown Source) ~[classes/:na]
at org.showpage.threadingtransactions.service.SubService.populate(SubService.java:44) ~[classes/:na]
at org.showpage.threadingtransactions.service.BookInfoService.lambda$spawnWillPass$1(BookInfoService.java:115) ~[classes/:na]
at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
</code>
<code>org.hibernate.LazyInitializationException: could not initialize proxy [org.showpage.threadingtransactions.dbmodel.Author#1] - no Session at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:165) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:314) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:44) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:102) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.showpage.threadingtransactions.dbmodel.Author$HibernateProxy$n6D0Du2E.getName(Unknown Source) ~[classes/:na] at org.showpage.threadingtransactions.service.SubService.populate(SubService.java:44) ~[classes/:na] at org.showpage.threadingtransactions.service.BookInfoService.lambda$spawnWillPass$1(BookInfoService.java:115) ~[classes/:na] at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na] </code>
org.hibernate.LazyInitializationException: could not initialize proxy [org.showpage.threadingtransactions.dbmodel.Author#1] - no Session
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:165) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:314) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
    at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:44) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
    at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:102) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
    at org.showpage.threadingtransactions.dbmodel.Author$HibernateProxy$n6D0Du2E.getName(Unknown Source) ~[classes/:na]
    at org.showpage.threadingtransactions.service.SubService.populate(SubService.java:44) ~[classes/:na]
    at org.showpage.threadingtransactions.service.BookInfoService.lambda$spawnWillPass$1(BookInfoService.java:115) ~[classes/:na]
    at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]

This is from a sample project I created to try to work on a general solution, which can be seen at https://github.com/jplflyer/SpringDataThreading.git.

My sample application is very small. I have a WebController.java:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>
@RestController
@RequiredArgsConstructor
@Slf4j
public class WebController {
private final AuthorRepository authorRepository;
private final BookRepository bookRepository;
private final BookInfoService bookInfoService;
@GetMapping("/request")
public ResponseEntity<String> makeRequest(
@RequestParam(required = false, defaultValue = "false") boolean pass,
@RequestParam(name = "no_spawn", required = false, defaultValue = "false") boolean noSpawn
) {
log.info("makeRequest({}, {})", pass, noSpawn);
BookInfoService.Mode mode = noSpawn
? BookInfoService.Mode.NoSpawn
: ( pass ? BookInfoService.Mode.SpawnWillPass : BookInfoService.Mode.SpawnWillFail);
int requestId = bookInfoService.makeRequest(mode);
return ResponseEntity.ok(String.format("%dn", requestId));
}
}
</code>
<code> @RestController @RequiredArgsConstructor @Slf4j public class WebController { private final AuthorRepository authorRepository; private final BookRepository bookRepository; private final BookInfoService bookInfoService; @GetMapping("/request") public ResponseEntity<String> makeRequest( @RequestParam(required = false, defaultValue = "false") boolean pass, @RequestParam(name = "no_spawn", required = false, defaultValue = "false") boolean noSpawn ) { log.info("makeRequest({}, {})", pass, noSpawn); BookInfoService.Mode mode = noSpawn ? BookInfoService.Mode.NoSpawn : ( pass ? BookInfoService.Mode.SpawnWillPass : BookInfoService.Mode.SpawnWillFail); int requestId = bookInfoService.makeRequest(mode); return ResponseEntity.ok(String.format("%dn", requestId)); } } </code>

@RestController
@RequiredArgsConstructor
@Slf4j
public class WebController {
    private final AuthorRepository authorRepository;
    private final BookRepository bookRepository;
    private final BookInfoService bookInfoService;

    @GetMapping("/request")
    public ResponseEntity<String> makeRequest(
            @RequestParam(required = false, defaultValue = "false") boolean pass,
            @RequestParam(name = "no_spawn", required = false, defaultValue = "false") boolean noSpawn
    ) {
        log.info("makeRequest({}, {})", pass, noSpawn);
        BookInfoService.Mode mode = noSpawn
                ? BookInfoService.Mode.NoSpawn
                : ( pass ? BookInfoService.Mode.SpawnWillPass : BookInfoService.Mode.SpawnWillFail);
        int requestId = bookInfoService.makeRequest(mode);
        return ResponseEntity.ok(String.format("%dn", requestId));
    }


}

BookInfoService.java is longer only because I’ve tried a bunch of ways to fix this:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>
@Service
@RequiredArgsConstructor
@Slf4j
public class BookInfoService {
public enum Mode {
SpawnWillFail,
SpawnWillPass,
NoSpawn
}
private final BookRepository bookRepository;
private final EntityManager entityManager;
private SessionFactory sessionFactory;
private static int requestId = 0;
/**
* Spawn the request.
*/
public int makeRequest(Mode mode) {
BookInfo info = BookInfo
.builder()
.requestId(++requestId)
.build();
switch (mode) {
case SpawnWillFail: spawnWillFail(info); break;
case SpawnWillPass: spawnWillPass(info); break;
case NoSpawn: runNoSpawn(info); break;
}
return info.getRequestId();
}
/**
* This version to prove it works if we don't run worker threads.
*/
private void runNoSpawn(BookInfo info) {
SubService subService = new SubService(entityManager, bookRepository);
try {
subService.populate(info);
}
catch (Exception e) {
log.error("Exception", e);
}
}
/**
* This version will fail with a lazy initialization exception.
*/
private void spawnWillFail(BookInfo info) {
List<Book> allBooks = bookRepository.findAll();
new Thread(() -> {
log.info("Bad Thread start");
populate(info, allBooks);
log.info("Bad Thread done");
}).start();
}
/**
* This version will pass once I get it to work but currently fails.
*/
private void spawnWillPass(BookInfo info) {
EntityManager em = entityManager
.getEntityManagerFactory()
.createEntityManager();
SessionFactory sessionFactory = em.getEntityManagerFactory().unwrap(SessionFactory.class);
SubService subService = new SubService(em, bookRepository);
new Thread(() -> {
log.info("Good Thread start");
try {
Session session = sessionFactory.openSession();
em.getTransaction().begin();
subService.populate(info);
em.getTransaction() .commit();
}
catch (Exception e) {
log.error("Exception", e);
em.getTransaction().rollback();
}
finally {
em.close();
sessionFactory.close();
}
log.info("Good Thread done");
}).start();
}
}
</code>
<code> @Service @RequiredArgsConstructor @Slf4j public class BookInfoService { public enum Mode { SpawnWillFail, SpawnWillPass, NoSpawn } private final BookRepository bookRepository; private final EntityManager entityManager; private SessionFactory sessionFactory; private static int requestId = 0; /** * Spawn the request. */ public int makeRequest(Mode mode) { BookInfo info = BookInfo .builder() .requestId(++requestId) .build(); switch (mode) { case SpawnWillFail: spawnWillFail(info); break; case SpawnWillPass: spawnWillPass(info); break; case NoSpawn: runNoSpawn(info); break; } return info.getRequestId(); } /** * This version to prove it works if we don't run worker threads. */ private void runNoSpawn(BookInfo info) { SubService subService = new SubService(entityManager, bookRepository); try { subService.populate(info); } catch (Exception e) { log.error("Exception", e); } } /** * This version will fail with a lazy initialization exception. */ private void spawnWillFail(BookInfo info) { List<Book> allBooks = bookRepository.findAll(); new Thread(() -> { log.info("Bad Thread start"); populate(info, allBooks); log.info("Bad Thread done"); }).start(); } /** * This version will pass once I get it to work but currently fails. */ private void spawnWillPass(BookInfo info) { EntityManager em = entityManager .getEntityManagerFactory() .createEntityManager(); SessionFactory sessionFactory = em.getEntityManagerFactory().unwrap(SessionFactory.class); SubService subService = new SubService(em, bookRepository); new Thread(() -> { log.info("Good Thread start"); try { Session session = sessionFactory.openSession(); em.getTransaction().begin(); subService.populate(info); em.getTransaction() .commit(); } catch (Exception e) { log.error("Exception", e); em.getTransaction().rollback(); } finally { em.close(); sessionFactory.close(); } log.info("Good Thread done"); }).start(); } } </code>

@Service
@RequiredArgsConstructor
@Slf4j
public class BookInfoService {
    public enum Mode {
        SpawnWillFail,
        SpawnWillPass,
        NoSpawn
    }

    private final BookRepository bookRepository;
    private final EntityManager entityManager;
    private SessionFactory sessionFactory;

    private static int requestId = 0;

    /**
     * Spawn the request.
     */
    public int makeRequest(Mode mode) {
        BookInfo info = BookInfo
                .builder()
                .requestId(++requestId)
                .build();

        switch (mode) {
            case SpawnWillFail: spawnWillFail(info); break;
            case SpawnWillPass: spawnWillPass(info); break;
            case NoSpawn: runNoSpawn(info); break;
        }
        return info.getRequestId();
    }

    /**
     * This version to prove it works if we don't run worker threads.
     */
    private void runNoSpawn(BookInfo info) {
        SubService subService = new SubService(entityManager, bookRepository);

        try {
            subService.populate(info);
        }
        catch (Exception e) {
            log.error("Exception", e);
        }
    }

    /**
     * This version will fail with a lazy initialization exception.
     */
    private void spawnWillFail(BookInfo info) {
        List<Book> allBooks = bookRepository.findAll();

        new Thread(() -> {
            log.info("Bad Thread start");
            populate(info, allBooks);
            log.info("Bad Thread done");
        }).start();
    }

    /**
     * This version will pass once I get it to work but currently fails.
     */
    private void spawnWillPass(BookInfo info) {
        EntityManager em = entityManager
                .getEntityManagerFactory()
                .createEntityManager();

        SessionFactory sessionFactory = em.getEntityManagerFactory().unwrap(SessionFactory.class);

        SubService subService = new SubService(em, bookRepository);

        new Thread(() -> {
            log.info("Good Thread start");

            try {
                Session session = sessionFactory.openSession();
                em.getTransaction().begin();
                subService.populate(info);
                em.getTransaction() .commit();
            }
            catch (Exception e) {
                log.error("Exception", e);
                em.getTransaction().rollback();
            }
            finally {
                em.close();
                sessionFactory.close();
            }

            log.info("Good Thread done");
        }).start();
    }
}

SubService.java is boring:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>
@RequiredArgsConstructor
@Slf4j
@Service
public class SubService {
@PersistenceContext
private final EntityManager em;
private final BookRepository bookRepository;
/**
* Worker for the above.
*/
@Transactional
public void populate(BookInfo info) {
List<Book> allBooks = bookRepository.findAll();
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
StringBuilder sb = new StringBuilder();
try {
for (Book book : allBooks) {
sb.append(
String.format("Name: %s Author: %sn",
book.getName(),
book.getAuthor().getName())
);
}
}
catch (Exception e) {
log.error("Exception", e);
}
info.setData(sb.toString());
info.setDone(true);
}
}
</code>
<code> @RequiredArgsConstructor @Slf4j @Service public class SubService { @PersistenceContext private final EntityManager em; private final BookRepository bookRepository; /** * Worker for the above. */ @Transactional public void populate(BookInfo info) { List<Book> allBooks = bookRepository.findAll(); try { Thread.sleep(1000); } catch (InterruptedException ignored) { } StringBuilder sb = new StringBuilder(); try { for (Book book : allBooks) { sb.append( String.format("Name: %s Author: %sn", book.getName(), book.getAuthor().getName()) ); } } catch (Exception e) { log.error("Exception", e); } info.setData(sb.toString()); info.setDone(true); } } </code>

@RequiredArgsConstructor
@Slf4j
@Service
public class SubService {

    @PersistenceContext
    private final EntityManager em;

    private final BookRepository bookRepository;

    /**
     * Worker for the above.
     */
    @Transactional
    public void populate(BookInfo info) {
        List<Book> allBooks = bookRepository.findAll();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException ignored) {
        }

        StringBuilder sb = new StringBuilder();

        try {
            for (Book book : allBooks) {
                sb.append(
                        String.format("Name: %s Author: %sn",
                                book.getName(),
                                book.getAuthor().getName())
                );
            }
        }
        catch (Exception e) {
            log.error("Exception", e);
        }

        info.setData(sb.toString());
        info.setDone(true);
    }
}

I have absolutely no beans configured, and my application.yml file is only enough to define the connection to the database.

I test this with:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>curl -s "http://localhost:8080/request?no_spawn=false&pass=true"
</code>
<code>curl -s "http://localhost:8080/request?no_spawn=false&pass=true" </code>
curl -s "http://localhost:8080/request?no_spawn=false&pass=true"

and then I watch the logs in IntelliJ.

Set no_spawn to true, and you get the don’t thread version. But this call is the one I’m trying to fix — spawning a thread, but it’s the one that actually tries to work.

I know that the @async flag might be a solution, but as I said — significant legacy code I’m trying to fix. I know I could Fetch-Eager, but that’s a particularly bad solution. I could prefetch All The Data, but that’s a lot of places where I’d have to handle prefetching before spawning.

Currently, we’re fixing these one at a time by using the repository directly instead of traversing the ORM. That is, instead of book.getAuthor().getName(), I would fetch the author from the AuthorRepository using book.getAuthorId(). But again, that involves finding them one by one, and there’s a lot of these.

I’ve read a couple of dozen “solutions”, which is where I found references to trying to use EntityManager as you see here as well as SessionFactory. But nothing I’ve done has actually solved the problem.

So… Is there some way I can solve the No Session failure?

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật