Ich habe gelernt, dass es möglich ist, Repository-Abfragen mithilfe der asynchronen Verarbeitung von Spring asynchron auszuführen, und habe daher untersucht, wie sie implementiert werden können. Dieser Artikel fasst eine einfache Implementierung asynchroner Abfragen und die Ergebnisse ihrer Operation zusammen.
Umgebung
Referenz
Erstellen Sie eine Ansicht, deren Suche lange dauert, um die Überprüfung des Vorgangs zu vereinfachen.
CREATE OR REPLACE VIEW async_test_view (
id
, sleep
, create_at ) AS
SELECT MD5(UUID()) AS id
, SLEEP(10) AS sleep
, NOW() AS create_at
;
Das Durchsuchen dieser Ansicht dauert ca. 10 Sekunden, um die Ergebnisse zurückzugeben.
> select * from pseudo_delay_view;
+----------------------------------+-------+---------------------+
| id | sleep | create_at |
+----------------------------------+-------+---------------------+
| da863db6ff1b064ebff03f00efdd224b | 0 | 2017-12-23 17:27:08 |
+----------------------------------+-------+---------------------+
1 row in set (10.00 sec)
@SpringBootApplication
// 1
@EnableAsync
public class DemoGradleApplication {
public static void main(String[] args) {
SpringApplication.run(DemoGradleApplication.class, args);
}
// 2
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 3
executor.setQueueCapacity(2);
// 4
executor.setCorePoolSize(2);
executor.setMaxPoolSize(3);
// 5
executor.setKeepAliveSeconds(10);
executor.afterPropertiesSet();
return executor;
}
}
Wenn die Anzahl der Anforderungen MaxPoolSize + QueueCapacity überschreitet, wird standardmäßig eine Ausnahme namens RejectedExecutionException ausgelöst.
Wenn Sie eine beliebige Verarbeitung für die Ablehnungsverarbeitung ausführen möchten, wenn die Anforderung die Obergrenze erreicht, übergeben Sie die Klasse, die die RejectedExecutionHandler-Schnittstelle implementiert, an ThreadPoolTaskExecutor, wie unten gezeigt. Wenn nicht angegeben, ist der Standardwert ThreadPoolExecutor # AbortPolicy. (Löst eine RejectedExecutionException-Ausnahme aus)
executor.setRejectedExecutionHandler((r,e) -> {
//Implementieren Sie jeden Prozess, den Sie ausführen möchten
throw new RejectedExecutionException("Task:" + r.toString() + " rejected from " + e.toString());
});
Eine Implementierung der Entität, die der Ansicht entspricht. Es gibt keine besonderen Hinweise.
@Entity
@Table(name="pseudo_delay_view")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PseudoDelay implements Serializable {
private static final long serialVersionUID = -9169553858944816379L;
@Id
private String id;
@Column(name="sleep", nullable=false)
private Integer sleep;
@Column(name="create_at", nullable=false)
private LocalDateTime createAt;
}
Eine Implementierung des Repositorys, das die Abfrage ausgibt. Implementiert die asynchrone Ausführung von Abfragen. Sie müssen lediglich die Async-Annotation zur Methode hinzufügen.
public interface PseudoDelayRepository extends JpaRepository<PseudoDelay, String> {
// 1
@Async
@Query("SELECT p FROM PseudoDelay AS p")
CompletableFuture<PseudoDelay> findAsync();
// 2
@Query("SELECT p FROM PseudoDelay AS p")
PseudoDelay findSync();
}
In diesem Beispiel wird CompletableFuture verwendet, aber zusätzlich können Future und Spring ListenableFuture verwendet werden. Weitere Informationen finden Sie unter Spring Data JPA - Referenzdokumentation --3.4.7. Asynchrone Abfrageergebnisse. In kann bestätigt werden.
Die Implementierung der Serviceklasse zur Bestätigung ist wie folgt.
@Service
@Slf4j
public class AsyncTestServiceImpl implements AsyncTestService {
private PseudoDelayRepository repository;
public AsyncTestServiceImpl(PseudoDelayRepository repository) {
this.repository = repository;
}
// 1
@Transactional(readOnly = true)
@Override
public PseudoDelay async() {
log.debug("start async");
CompletableFuture<PseudoDelay> future = repository.findAsync();
//Führen Sie etwas aus, während Sie die Abfrage asynchron ausführen
log.debug("execute somethings");
PseudoDelay result = null;
try {
//Empfangen Sie das Ergebnis der Abfrageausführung
result = future.thenApply(res -> {
log.debug("async result : {}", res);
return res;
})
.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
log.debug("end async");
return result;
}
// 2
@Transactional(readOnly = true)
@Override
public PseudoDelay sync() {
log.debug("start sync");
PseudoDelay result = repository.findSync();
log.debug("sync result : {}", result);
log.debug("end sync");
return result;
}
}
Sie können sehen, dass eine andere Verarbeitung ("*** etwas ausführen ***") unmittelbar nach dem Ausgeben der Abfrage ausgeführt wird.
2017-12-23 19:55:36.194 DEBUG 5304 --- [nio-9000-exec-4] : start async
2017-12-23 19:55:36.195 DEBUG 5304 --- [nio-9000-exec-4] : *** execute somethings ***
2017-12-23 19:55:46.198 DEBUG 5304 --- [ taskExecutor-2] : async result : PseudoDelay(id=9904388341a9d8dbdfb230fb5b675224, sleep=0, createAt=2017-12-23T19:55:36)
2017-12-23 19:55:46.199 DEBUG 5304 --- [nio-9000-exec-4] : end async
Sie können sehen, dass es blockiert ist, bis das Ergebnis der Abfrage zurückgegeben wird.
2017-12-23 19:57:49.465 DEBUG 5304 --- [nio-9000-exec-8] : start sync
2017-12-23 19:57:59.467 DEBUG 5304 --- [nio-9000-exec-8] : sync result : PseudoDelay(id=3a19a242c0207cd9ddad551ec2ccae66, sleep=0, createAt=2017-12-23T19:57:49)
2017-12-23 19:57:59.467 DEBUG 5304 --- [nio-9000-exec-8] : end sync
Sie können das Zeitlimit angeben. Wenn es nicht innerhalb der angegebenen Zeit abgeschlossen wird, wird eine TimeoutException-Ausnahme ausgelöst.
result = future.get(5, TimeUnit.SECONDS);
Wir haben ein Zeitlimit für die Transaktion festgelegt und untersucht, was passieren würde, wenn eine Abfrage, die länger als dieses Zeitlimit dauerte, asynchron ausgeführt würde. Dieses Mal habe ich das Transaktionszeitlimit auf 5 Sekunden festgelegt und eine Abfrage ausgeführt, die sowohl asynchron als auch synchron 10 Sekunden dauerte, wie im obigen Beispiel.
@Transactional(readOnly = true, timeout = 5)
** Für asynchrone Abfragen **
Wenn das Zeitlimit überschritten wurde, wurde keine Ausnahme ausgelöst und das erwartete Ergebnis wurde nicht erreicht. Ich kenne die Ursache nicht, aber es scheint, dass wenn Sie es in einem anderen Thread innerhalb der Transaktion ausführen, die Transaktionsverwaltung nicht mehr möglich ist. Ich habe überprüft, ob das offizielle Dokument eine Beschreibung enthält, aber ich habe sie nicht vollständig überprüft. Vorerst gab es in einem solchen Artikel Spring @ Async und Transaktionsmanagement.
** Für synchrone Abfragen **
Ich denke, es hängt von der Implementierung des JDBC-Treibers ab, aber im Fall von MySQL tritt der folgende Fehler auf.
2017-12-23 20:17:18.297 ERROR 2260 --- [nio-9000-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : Statement cancelled due to timeout or client request
2017-12-23 20:17:18.319 ERROR 2260 --- [nio-9000-exec-1] o.a.c.c.C.[.[.[.[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [/app] threw exception [Request processing failed; nested exception is org.springframework.orm.jpa.JpaSystemException: could not extract ResultSet; nested exception is org.hibernate.exception.GenericJDBCException: could not extract ResultSet] with root cause
com.mysql.jdbc.exceptions.MySQLTimeoutException: Statement cancelled due to timeout or client request
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2827) ~[mysql-connector-java-5.1.44.jar:5.1.44]
/...Kürzung
Recommended Posts