[ Multi Tasking ][ Spring ][ Entity Manager ][ Transactions ]

0

Czesć mam problem z wątkami.
Kod poniej:

public class SQLFileQueryService {

    @PersistenceContext
    protected EntityManager entityManager;

    @Autowired
    private TransactionTemplate transactionTemplate;

    private void executeSQLScript(String scriptName) {

       transactionTemplate.execute(new TransactionCallback<>() {

           @Override
            public Void doInTransaction(TransactionStatus status) {
                InputStream fis = this.getClass().getClassLoader().getResourceAsStream(scriptName);
                String line;
                try (BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
                    while ((line = br.readLine()) != null) {
                        // try to
                        entityManager.createNativeQuery(line).executeUpdate();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
        });
    }


    @Transactional
    public void executeScriptsSql(String... scripts) {
        // set some flag off
        CompletableFuture<Void>[] tasks = new CompletableFuture[scripts.length];
        IntStream.range(0, tasks.length).forEach(i -> tasks[i] = CompletableFuture.runAsync(() -> executeSQLScript(scripts[i])));
        CompletableFuture.allOf(tasks).join(); // zatrzymuje główny wątek 
        /*
        wait for all task
        set some flag on
        */
  }
}

próbowałem :

1 .CompletableFuture.allOf(tasks).join(); // zawiesza dzialanie wątków w transakcji 
2.  while (currentInUseThreadCount.get() > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        } // również zawiesza 

Ogólnie to chciałem ominąć problem braku wsparcia wielowątkowości dla transactional w springu, stąd też pojawia się TransactionCallback.
Moim celem jest wyłączenie flagi do sprawdzania kluczy ( by nie rzucało wyjątkami ), wrzucenie wszytkich sql'ek z plików i ponowne włączenie flagi.
Jakieś mądre wskazówki ?
Jeśli przepuszczę główny wątek ( nie będę czekał na dzieciaki odpaane w executeSQLScript) to przy próbie odpalenia this.entityManager.createNativeQuery(enableFlagString).executeUpdate(); przez ostatni aktywny wątek entity manager jest nulowany.

3

Aaaaaaaaah!!!!!!!Aż mnie oczy bolą od takiego kodu!

2

1 ) J/W Oczy bolą od tego :/ Albo to popraw, albo daj kase ze prace w cięzkich warunkach
2)

        ExecutorService executor = Executors.newFixedThreadPool(Math.min(scripts.length, MAX_DATABASE_THREADS_POOL_SIZE));

Co za raczyszko, ExecutorService nie powien być zmienną lokalną i musi być później zamknięty :D
3)Czemu używasz EntityManagera?
Na więcej teraz nie odpowiem, jak dostane raka to będzie Twoja wina.

2

Generalnie to wątki i Spring nie działają za dobrze razem. (Ogólniej to Spring i Java też za dobrze razem nie działają :-)).
Używanie EntityManagera z różnych wątków to coś co raczej nie zadziała (raczej, nie chce mi się już wnikać).

Ale nie ma takiego problemu w springu, którego by się nie dało rozwiązać przez dorzucenie kilku adnotacji:
Użyj Springowego Executora, albo @Async
https://www.baeldung.com/spring-async

Btw. według mnie ładowanie skryptów w wątkach może niewiele dać w praktyce. Może miałoby sens na dobrej maszynie z dużą ilością dysków.
Baza danych to nie jest magiczna kostka, która przyjmuje dowolną liczbę SQLi na raz, też jest zlimitowana przez IO.
Jakkolwiek nie wiem - warto sprawdzić twój przypadek. Ciekawi mnie, jak już zadziała to pomierz to i napisz (np. wersja jedno i wielowątkowa).

catch (Exception e) {
                   e.printStackTrace();
}

to nie wygląda na dobry pomysł.

Ja bym robił tego typu kod bez Springa oczywiście - prościej i czyściej. Ba, w ogóle bym tego kodu nie robił, tylko użył np. Liquibase.
Ludzie korzystają z tego wyłaczania FK:
http://wavecos.github.io/post/2016-04-14-disabling-constraints-liquibase/

1

Persistence context jest spięty z transakcją, która ma zasięg jednego wątku. Tworząc wiele wątków, nawet przez @Async „tracisz transakcję” - przecież z punktu widzenia JPA i TransactionManagera nie wiadomo, kiedy zadanie będzie wykonywane.

Nie wgryzałem się w ten kod, bo raczysko, ale jeśli to ka działać, to musisz mieć transakcję per wątek i zadbać o to, żeby nie modyfikować tych samych encji w różnych taskach.

3

Jeśli używasz Spring Boota, i faktycznie liquibase / flyway nie załatwia sprawy (bo to nie jest tak, że np masz konkretny skrypt i chcesz go wykonać, tylko ktoś Ci dostarczy skrypt który masz wykonać), to taki kod jest nawet prosty:

ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator();
    resourceDatabasePopulator.addScript(new ClassPathResource("/your_script.sql"));
    resourceDatabasePopulator.setSeparator(ScriptUtils.EOF_STATEMENT_SEPARATOR);
    resourceDatabasePopulator.execute(dataSource);

gdzie dataSource to bean DataSource

1 użytkowników online, w tym zalogowanych: 0, gości: 1