sql >> Database teknologi >  >> RDS >> Mysql

Brug af tråde til at lave databaseanmodninger

Tråderegler for JavaFX

Der er to grundlæggende regler for tråde og JavaFX:

  1. Enhver kode, der ændrer eller får adgang til tilstanden af ​​en node, der er en del af en scenegraf, skal udføres på JavaFX-applikationstråden. Visse andre handlinger (f.eks. oprettelse af ny Stage s) er også bundet af denne regel.
  2. Enhver kode, der kan tage lang tid at køre bør udføres på en baggrundstråd (dvs. ikke på FX Application Thread).

Grunden til den første regel er, at rammen, som de fleste UI-værktøjssæt, er skrevet uden nogen synkronisering på tilstanden af ​​elementer i scenegrafen. Tilføjelse af synkronisering medfører en ydeevneomkostning, og dette viser sig at være en uoverkommelig omkostning for UI-værktøjssæt. Således kan kun én tråd sikkert få adgang til denne tilstand. Da UI-tråden (FX Application Thread for JavaFX) skal have adgang til denne tilstand for at gengive scenen, er FX Application Thread den eneste tråd, hvorpå du kan få adgang til "live" scenegraftilstand. I JavaFX 8 og nyere udfører de fleste metoder, der er underlagt denne regel, kontroller og afgiver runtime-undtagelser, hvis reglen overtrædes. (Dette er i modsætning til Swing, hvor du kan skrive "ulovlig" kode, og den kan se ud til at køre fint, men er faktisk udsat for tilfældige og uforudsigelige fejl på vilkårligt tidspunkt.) Dette er årsagen til IllegalStateException du ser :du kalder courseCodeLbl.setText(...) fra en anden tråd end FX Application Thread.

Grunden til den anden regel er, at FX Application Thread, ud over at være ansvarlig for at behandle brugerhændelser, også er ansvarlig for at gengive scenen. Så hvis du udfører en langvarig operation på den tråd, vil brugergrænsefladen ikke blive gengivet, før den handling er fuldført, og vil ikke reagere på brugerhændelser. Selvom dette ikke genererer undtagelser eller forårsager korrupt objekttilstand (som overtrædelse af regel 1 vil), skaber det (i bedste fald) en dårlig brugeroplevelse.

Så hvis du har en langvarig operation (såsom adgang til en database), der skal opdatere brugergrænsefladen efter færdiggørelsen, er den grundlæggende plan at udføre den langvarige operation i en baggrundstråd, og returnere resultaterne af operationen, når den er fuldfør, og planlæg derefter en opdatering af brugergrænsefladen i tråden for brugergrænsefladen (FX Application). Alle enkelttrådede UI-værktøjssæt har en mekanisme til at gøre dette:i JavaFX kan du gøre det ved at kalde Platform.runLater(Runnable r) for at udføre r.run() på FX Application Thread. (I Swing kan du kalde SwingUtilities.invokeLater(Runnable r) for at udføre r.run() på AWT-hændelsesforsendelsestråden.) JavaFX (se senere i dette svar) giver også et højere niveau API til at styre kommunikationen tilbage til FX Application Thread.

Generel god praksis for multithreading

Den bedste praksis for at arbejde med flere tråde er at strukturere kode, der skal udføres på en "brugerdefineret" tråd som et objekt, der er initialiseret med en eller anden fast tilstand, har en metode til at udføre operationen, og ved færdiggørelse returnerer et objekt repræsenterer resultatet. Det er yderst ønskeligt at bruge uforanderlige objekter til den initialiserede tilstand og beregningsresultatet. Ideen her er at eliminere muligheden for, at enhver foranderlig tilstand er synlig fra flere tråde så vidt muligt. Adgang til data fra en database passer fint til dette formsprog:du kan initialisere dit "arbejder"-objekt med parametrene for databaseadgang (søgeord osv.). Udfør databaseforespørgslen og få et resultatsæt, brug resultatsættet til at udfylde en samling af domæneobjekter, og returner samlingen til sidst.

I nogle tilfælde vil det være nødvendigt at dele foranderlig tilstand mellem flere tråde. Når dette absolut skal gøres, skal du omhyggeligt synkronisere adgangen til den pågældende tilstand for at undgå at observere tilstanden i en inkonsekvent tilstand (der er andre mere subtile problemer, der skal løses, såsom statens livlighed osv.). Den stærke anbefaling, når dette er nødvendigt, er at bruge et bibliotek på højt niveau til at administrere disse kompleksiteter for dig.

Brug af javafx.concurrent API

JavaFX leverer en concurrency API der er designet til at udføre kode i en baggrundstråd, med API specifikt designet til opdatering af JavaFX UI ved afslutning af (eller under) eksekvering af denne kode. Denne API er designet til at interagere med java.util.concurrent API , som giver generelle faciliteter til at skrive multithreaded kode (men uden UI-hooks). Nøgleklassen i javafx.concurrent er Opgave , som repræsenterer en enkelt, enkeltstående, arbejdsenhed beregnet til at blive udført på en baggrundstråd. Denne klasse definerer en enkelt abstrakt metode, call() , som ikke tager nogen parametre, returnerer et resultat og kan kaste kontrollerede undtagelser. Opgave implementerer Kørbar med dens run() metode, der blot kalder call() . Opgave har også en samling metoder, som med garanti opdaterer tilstanden på FX Application Thread, såsom updateProgress(...) , updateMessage(...) osv. Den definerer nogle observerbare egenskaber (f.eks. tilstand og værdi ):lyttere til disse egenskaber vil blive underrettet om ændringer på FX Application Thread. Endelig er der nogle bekvemme metoder til at registrere handlere (setOnSucceeded(...) , setOnFailed(...) , etc); alle behandlere, der er registreret via disse metoder, vil også blive kaldt på FX Application Thread.

Så den generelle formel for at hente data fra en database er:

  1. Opret en Opgave for at håndtere opkaldet til databasen.
  2. Initialiser Opgaven med enhver tilstand, der er nødvendig for at udføre databasekaldet.
  3. Implementer opgavens call() metode til at udføre databasekaldet og returnere resultaterne af opkaldet.
  4. Registrer en behandler med opgaven for at sende resultaterne til brugergrænsefladen, når den er fuldført.
  5. Bring opgaven på en baggrundstråd.

Til databaseadgang anbefaler jeg kraftigt at indkapsle den faktiske databasekode i en separat klasse, der ikke ved noget om brugergrænsefladen ( Data Access Object design mønster ). Lad derefter opgaven påkalde metoderne på dataadgangsobjektet.

Så du har måske en DAO-klasse som denne (bemærk, at der ikke er nogen UI-kode her):

public class WidgetDAO {

    // In real life, you might want a connection pool here, though for
    // desktop applications a single connection often suffices:
    private Connection conn ;

    public WidgetDAO() throws Exception {
        conn = ... ; // initialize connection (or connection pool...)
    }

    public List<Widget> getWidgetsByType(String type) throws SQLException {
        try (PreparedStatement pstmt = conn.prepareStatement("select * from widget where type = ?")) {
            pstmt.setString(1, type);
            ResultSet rs = pstmt.executeQuery();
            List<Widget> widgets = new ArrayList<>();
            while (rs.next()) {
                Widget widget = new Widget();
                widget.setName(rs.getString("name"));
                widget.setNumberOfBigRedButtons(rs.getString("btnCount"));
                // ...
                widgets.add(widget);
            }
            return widgets ;
        }
    }

    // ...

    public void shutdown() throws Exception {
        conn.close();
    }
}

Hentning af en masse widgets kan tage lang tid, så alle opkald fra en UI-klasse (f.eks. en controller-klasse) bør planlægge dette i en baggrundstråd. En controller-klasse kan se sådan ud:

public class MyController {

    private WidgetDAO widgetAccessor ;

    // java.util.concurrent.Executor typically provides a pool of threads...
    private Executor exec ;

    @FXML
    private TextField widgetTypeSearchField ;

    @FXML
    private TableView<Widget> widgetTable ;

    public void initialize() throws Exception {
        widgetAccessor = new WidgetDAO();

        // create executor that uses daemon threads:
        exec = Executors.newCachedThreadPool(runnable -> {
            Thread t = new Thread(runnable);
            t.setDaemon(true);
            return t ;
        });
    }

    // handle search button:
    @FXML
    public void searchWidgets() {
        final String searchString = widgetTypeSearchField.getText();
        Task<List<Widget>> widgetSearchTask = new Task<List<Widget>>() {
            @Override
            public List<Widget> call() throws Exception {
                return widgetAccessor.getWidgetsByType(searchString);
            }
        };

        widgetSearchTask.setOnFailed(e -> {
           widgetSearchTask.getException().printStackTrace();
            // inform user of error...
        });

        widgetSearchTask.setOnSucceeded(e -> 
            // Task.getValue() gives the value returned from call()...
            widgetTable.getItems().setAll(widgetSearchTask.getValue()));

        // run the task using a thread from the thread pool:
        exec.execute(widgetSearchTask);
    }

    // ...
}

Bemærk, hvordan opkaldet til den (potentielt) langvarige DAO-metode er pakket ind i en Opgave som køres på en baggrundstråd (via accessoren) for at forhindre blokering af brugergrænsefladen (regel 2 ovenfor). Opdateringen til brugergrænsefladen (widgetTable.setItems(...) ) udføres faktisk tilbage på FX Application Thread ved hjælp af Task 's bekvemmeligheds-tilbagekaldsmetode setOnSucceeded(...) (opfylder regel 1).

I dit tilfælde returnerer den databaseadgang, du udfører, et enkelt resultat, så du kan have en metode som

public class MyDAO {

    private Connection conn ; 

    // constructor etc...

    public Course getCourseByCode(int code) throws SQLException {
        try (PreparedStatement pstmt = conn.prepareStatement("select * from course where c_code = ?")) {
            pstmt.setInt(1, code);
            ResultSet results = pstmt.executeQuery();
            if (results.next()) {
                Course course = new Course();
                course.setName(results.getString("c_name"));
                // etc...
                return course ;
            } else {
                // maybe throw an exception if you want to insist course with given code exists
                // or consider using Optional<Course>...
                return null ;
            }
        }
    }

    // ...
}

Og så ville din controller-kode se sådan ud

final int courseCode = Integer.valueOf(courseId.getText());
Task<Course> courseTask = new Task<Course>() {
    @Override
    public Course call() throws Exception {
        return myDAO.getCourseByCode(courseCode);
    }
};
courseTask.setOnSucceeded(e -> {
    Course course = courseTask.getCourse();
    if (course != null) {
        courseCodeLbl.setText(course.getName());
    }
});
exec.execute(courseTask);

API-dokumenter til Task har mange flere eksempler, inklusive opdatering af fremskridt egenskaben for opgaven (nyttigt til statuslinjer... osv.



  1. SQL Server Indsæt, hvis den ikke findes

  2. Opgradere PostgreSQL JSON-kolonnen til JSONB?

  3. Oracle DB citat kolonnenavne

  4. At bruge if(isset($_POST['submit'])) til ikke at vise ekko, når scriptet er åbent, virker ikke