In einem realen Anwendungsszenario wird ein großer Teil der Verarbeitung auf dem Backend-Server durchgeführt, wo die Daten tatsächlich verarbeitet und in einem Repository gespeichert werden. Abgesehen von vielen herausragenden Funktionen von Spring, wie DI (Dependency Injection), Aspects und POJO-orientierter Entwicklung, bietet Spring eine hervorragende Unterstützung für die Datenverarbeitung. Es gibt verschiedene Möglichkeiten, gute Datenbankanwendungen zu schreiben. Noch heute wird eine große Anzahl von Anwendungen basierend auf der JDBC-Datenzugriffsfähigkeit geschrieben. Dieser Artikel befasst sich speziell mit dem JDBC in Verbindung mit Spring, seiner Unterstützung und den Vor- und Nachteilen mit entsprechenden Beispielen und Codeschnipseln.
JDBC-Übersicht
Einer der größten Vorteile der Verwendung von JDBC in der ORM-Welt besteht darin, dass es nicht erforderlich ist, die Abfragesprache eines anderen Frameworks zu beherrschen, abgesehen von der Arbeit mit Daten auf einer viel niedrigeren Ebene. Es ermöglicht einem Programmierer, die proprietären Funktionen der Datenbank zu nutzen. Es hat auch seine Nachteile. Leider sind die Nachteile oft so sichtbar, dass sie keiner Erwähnung bedürfen. Einer davon ist beispielsweise Boilerplate-Code . Der Begriff Boilerplate-Code bedeutet im Grunde, denselben Code immer wieder zu schreiben, ohne einen Wert in den Code aufzunehmen. Dies ist typischerweise zu sehen, wenn wir Daten aus einer Datenbank abfragen; Im folgenden Code rufen wir beispielsweise einfach einen Benutzer ab Eintrag aus der Datenbank.
public User getUserById(long id) { User user = null; Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; try { con = dataSource.getConnection(); pstmt = con.prepareStatement("select * from " + "user_table where userid=?"); pstmt.setInt(1, id); rs.pstmt.executeQuery(); if (rs.next()) { user = new User(); user.setId(rs.getInt("userid")); user.setFullName(rs.getString("fullname")); user.setUserType(rs.getString("usertype")); user.setPassword(rs.getString("password")); } } catch (SQLException ex1) {} finally { try { if (rs != null) rs.close(); if (pstmt != null) rs.close(); if (con != null) rs.close(); } catch (SQLException ex2) {} } return user; }
Beachten Sie, dass wir jedes Mal, wenn wir mit der Datenbank interagieren müssen, drei Objekte erstellen müssen – eine Verbindung (Connection ), Anweisung (PreparedStatement ) und Ergebnismenge (ResultSet ). All dies muss auch in den auferlegten try…catch eingeschlossen werden Block. Auch das Schließen der Verbindung muss in try…catch eingeschlossen werden . Das ist lächerlich, weil der tatsächlich erforderliche Code für die Funktion viel geringer ist. Der Code wird einfach mit unnötigem, aber obligatorischem Code aufgeblasen und muss überall dort wiederholt werden, wo wir mit der Datenbank interagieren. Ein cleveres Codierungsschema kann dieses Durcheinander reduzieren, aber es ist unmöglich, das Problem des Boilerplate-JDBC-Codes zu beseitigen. Das ist nicht nur das Problem mit JDBC, sondern auch mit JMS, JNDI und REST.
Frühlingslösung
Das Spring-Framework bot eine Lösung für dieses Durcheinander und bot eine Möglichkeit, Boilerplate-Code durch die Verwendung von Template-Klassen zu eliminieren. Diese Klassen kapseln den Boilerplate-Code und entlasten so den Programmierer. Das bedeutet, dass der Boilerplate-Code noch vorhanden ist, nur der Programmierer, der eine der Template-Klassen verwendet, wird von der Mühe des Schreibens befreit. Das JdbcTemplate bereitgestellt von Spring ist die zentrale Klasse des JDBC-Kernpakets.
Es vereinfacht die Verwendung von JDBC und hilft, häufige Fehler zu vermeiden. Es führt den JDBC-Kernworkflow aus und lässt den Anwendungscode zurück, um SQL bereitzustellen und Ergebnisse zu extrahieren. Diese Klasse führt SQL-Abfragen oder -Aktualisierungen aus, initiiert eine Iteration über ResultSets und fängt JDBC-Ausnahmen ab und übersetzt sie in die generische, informativere Ausnahmehierarchie, die in org.springframework.dao definiert ist Paket.
Wir müssen nur die Rückrufschnittstellen implementieren und ihnen einen klar definierten Vertrag geben. Zum Beispiel der PreparedStatementCreator Die Callback-Schnittstelle wird zum Erstellen einer vorbereiteten Anweisung verwendet. Der ResultsetExtractor Schnittstelle verhält sich wie ein ResultSet .
Daher kann das vorhergehende Code-Snippet mit JdbcTemplate umgeschrieben werden wie folgt:
@Autowired private JdbcTemplate jdbcTemplate; public User getUserById(long id) { return jdbcTemplate.queryForObject( "select * from user_table where userid=?", new UserRowMapper(),id); } class UserRowMapper implements RowMapper<User>{ @Override public User mapRow(ResultSet rs, int runNumber) throws SQLException { User user=new User(); user.setId(rs.getInt("userid")); user.setFullName(rs.getString("fullname")); user.setUserType(rs.getString("usertype")); user.setPassword(rs.getString("password")); return user; } }
Der RowMapper ist eine Schnittstelle, die typischerweise von JdbcTemplate verwendet wird um eine Zeile pro Zeilenbasis des ResultSet abzubilden . Der RowMapper Objekte sind zustandslos und daher wiederverwendbar. Sie eignen sich perfekt für die Implementierung einer beliebigen Zeilenzuordnungslogik. Beachten Sie, dass wir im vorherigen Code Ausnahmen nicht explizit behandelt haben, wie wir es im Code getan haben, der JdbcTemplate nicht verwendet . Die RowMapper-Implementierung führt die eigentliche Implementierung der Zuordnung jeder Zeile zum Ergebnisobjekt durch, ohne dass sich der Programmierer um die Ausnahmebehandlung kümmern muss. Es wird aufgerufen und verarbeitet, indem JdbcTemplate aufgerufen wird .
Die Ausnahmen
Die von JDBC bereitgestellten Ausnahmen sind oft zu imposant als nötig und haben wenig Wert. Die Datenzugriffs-Ausnahmehierarchie von Spring ist in dieser Hinsicht schlanker und vernünftiger. Dies bedeutet, dass es einen konsistenten Satz von Ausnahmeklassen in seinem Arsenal hat, im Gegensatz zu JDBCs One-Size-Fit-to-All-Ausnahme namens SQLException für alle Probleme rund um den Datenzugriff. Die Datenzugriffsausnahmen von Spring basieren auf der DataAccessException Klasse. Daher können wir sowohl die Wahl zwischen geprüfter als auch ungeprüfter Ausnahme in das Framework einbetten. Das klingt praktischer, weil es für viele der Probleme, die während des Datenzugriffs zur Laufzeit aufgetreten sind, wirklich keine Lösungen gibt und es sinnlos ist, sie aufzufangen, wenn wir die Situation nicht mit einer geeigneten Alternative angehen können.
Springs Methode zur Vereinfachung des Datenzugriffs
Was Spring tatsächlich tut, ist, dass es den festen und den variablen Teil des Datenzugriffsmechanismus in zwei Gruppen von Klassen unterscheidet, die Vorlagenklassen genannt werden und Callback-Klassen , bzw. Der feste Teil des Codes stellt den oberflächlichen Teil des Datenzugriffs dar und der variable Teil ist die Datenzugriffsmethode, die je nach sich ändernder Anforderung variiert.
Kurz gesagt, die Vorlagenklassen Griff:
- Transaktionskontrolle
- Ressourcenverwaltung
- Ausnahmebehandlung
Und die Callback-Klassen Griff:
- Abfrageanweisung erstellen
- Parameterbindung
- Ergebnissatz-Marshalling
Wir können je nach Wahl der verwendeten persistenten Technologie eine aus vielen Template-Klassen auswählen. Beispielsweise können wir für JDBC JdbcTemplate wählen , oder für ORM können wir JpaTemplate wählen , Template für Ruhezustand , und so weiter.
Während wir uns jetzt mit der Datenbank verbinden, haben wir drei Optionen, um die Datenquelle zu konfigurieren, wie zum Beispiel:
- Vom JDBC-Treiber definiert
- Nachgeschlagen von JNDI
- Aus Verbindungspool abgerufen
Eine produktionsbereite Anwendung verwendet normalerweise einen Verbindungspool oder JNDI. Die vom JDBC-Treiber definierten Datenquellen sind bei weitem die einfachsten, obwohl sie hauptsächlich zu Testzwecken verwendet werden. Spring bietet drei Klassen im Paket org.springframework.jdbc.datasource an dieser Kategorie; sie sind:
- DriverManagerDataSource: Einfache Implementierung der Standard JDBC DataSource Schnittstelle, Konfiguration des einfachen alten JDBC DriverManager über Bean-Eigenschaften und Rückgabe einer neuen Verbindung von jeder Anfrage.
- SingleConnectionDataSource: Gibt bei jeder Anfrage dieselbe Verbindung zurück. Diese Art der Verbindung ist in erster Linie zum Testen gedacht.
- SimpleDriverDataSource: Das gleiche wie DriverManagerDataSource außer dass es spezielle Klassenladeprobleme wie OSGi gibt; diese Klasse arbeitet direkt mit dem JDBC-Treiber.
Die Konfiguration dieser Datenquellen ist ähnlich. Wir können sie in einer Bean-Klasse oder über XML konfigurieren.
// Configuring MySQL data source @Bean public DataSource dataSource() { DriverManagerDataSource ds=new DriverManagerDataSource(); ds.setDriverClassName("com.mysql.jdbc.Driver"); ds.setUrl("jdbc:mysql://localhost:3306/testdb"); ds.setUsername("root"); ds.setPassword("secret"); return ds; } <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" p_driverClassName="com.mysql.jdbc.Driver" p_url="jdbc:mysql://localhost:3306/testdb" p_username="root" p_password="secret"/>
JDBC-Vorlagenklassen
Spring bietet einige Vorlagenklassen, um den Datenzugriff mit JDBC zu vereinfachen:
- JdbcTemplate: Dies ist die grundlegende Klasse des Kern-JDBC-Pakets org.springframework.jdbc.core die den einfachsten Zugriff auf die Datenbank durch indizierte Abfragen bietet.
- NamedParameterJdbcTemplate: Diese Vorlagenklasse bietet auch einen grundlegenden Satz von JDBC-Operationen, bei denen die Werte mit benannten Parametern und nicht mit herkömmlichen „?“-Platzhaltern in SQL-Abfragen gebunden werden.
JDBC-Callback-Klassen
Die wichtigsten JDBC Callback-Funktionsschnittstellen, die in org.springframework.jdbc.core definiert sind sind:
- CallableStatementCallback
: Arbeitet mit JDBC CallableStatement. Dieser Callback wird intern von JdbcTemplate verwendet und ermöglicht die Ausführung auf einem einzigen CallableStatement B. einzelne oder mehrere SQL-Ausführungsaufrufe mit unterschiedlichen Parametern. - PreparedStatementCallback
: Funktioniert auf JDBC PreparedStatement. Dieser Callback wird intern von JdbcTemplate verwendet und ermöglicht die Ausführung von mehr als einer Operation auf einem einzigen PreparedStatement B. einzelner oder mehrfacher SQL executeUpdate-Aufruf mit unterschiedlichen Parametern. - StatementCallback
: Arbeitet mit der JDBC Anweisung . Dieser Callback wird auch intern von JdbcTemplate verwendet um mehr als eine Operation auf einer einzigen Anweisung auszuführen B. einzelne oder mehrere SQL executeUpdate-Aufrufe.
Ein einfaches Spring Boot JDBC-Beispiel
Versuchen wir es mit einem einfachen Spring-Boot-Beispiel. Ein Spring Boot-Projekt handhabt automatisch viele der Komplexitäten der Konfiguration, bei denen ein Entwickler von allen Problemen befreit wird, sobald eine korrekte Abhängigkeit in die Maven-Datei pom.xml aufgenommen wurde . Um die Länge des Artikels kurz zu halten, enthalten wir keine Code-Erklärungen. Bitte verwenden Sie die am Ende des Artikels angegebenen Referenzen für eine genauere Beschreibung.
Um am folgenden Beispiel zu arbeiten, erstellen Sie wie folgt eine Datenbank und eine Tabelle in MySQl:
Melden Sie sich bei der MySQL-Datenbank an und erstellen Sie eine Datenbank und eine Tabelle mit dem folgenden Befehl:
CREATE DATABASE testdb; USE testdb; CREATE TABLE candidate( id INT UNSIGNED NOT NULL AUTO_INCREMENT, fullname VARCHAR(100) NOT NULL, email VARCHAR(100) NOT NULL, phone VARCHAR(10) NOT NULL, PRIMARY KEY(id) );
Starten Sie als Spring-Starter-Projekt aus der Spring Tool Suite (STS) mit der Abhängigkeit JDBC und MySQL. Die Maven-Konfigurationsdatei pom.xml , des Projekts lautet wie folgt:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns_xsi="http://www.w3.org/2001/XMLSchema-instance" xsi_schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.mano.springbootjdbc.demo</groupId> <artifactId>spring-boot-jdbc-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>spring-boot-jdbc-demo</name> <description>Demo project for Spring Boot jdbc</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.10.RELEASE</version> <relativePath/> <!-- Look up parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8 </project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8 </project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven- plugin</artifactId> </plugin> </plugins> </build> </project>
Modellklasse:Candidate.java
package org.mano.springbootjdbc.demo.model; public class Candidate { private int id; private String fullname; private String email; private String phone; public Candidate() { super(); } public Candidate(int id, String fullname, String email, String phone) { super(); setId(id); setFullname(fullname); setEmail(email); setPhone(phone); } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getFullname() { return fullname; } public void setFullname(String fullname) { this.fullname = fullname; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Override public String toString() { return "Candidate [id=" + id + ", fullname=" + fullname + ", email=" + email + ", phone=" + phone + "]"; } }
Datenzugriffsobjektschnittstelle:CandidateDao.java
package org.mano.springbootjdbc.demo.dao; import java.util.List; import org.mano.springbootjdbc.demo.model.Candidate; public interface CandidateDao { public void addCandidate(Candidate candidate); public void modifyCandidate(Candidate candidate, int candidateId); public void deleteCandidate(int candidateId); public Candidate find(int candidateId); public List<Candidate> findAll(); }
Datenzugriffsobjekt-Implementierungsklasse:CandidateDaoImpl.java
package org.mano.springbootjdbc.demo.dao; import java.util.ArrayList; import java.util.List; import org.mano.springbootjdbc.demo.model.Candidate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository @Qualifier("candidateDao") public class CandidateDaoImpl implements CandidateDao { @Autowired JdbcTemplate jdbcTemplate; @Override public void addCandidate(Candidate candidate) { jdbcTemplate.update("insert into candidate (id,fullname,email,phone) " + "values (?,?,?,?)", candidate.getId(), candidate.getFullname(), candidate.getEmail(), candidate.getPhone()); System.out.println(candidate+" is added successfully!"); } @Override public void modifyCandidate(Candidate candidate, int candidateId) { jdbcTemplate.update("update candidate fullname=?, email=?,phone=? " + "where id=? values (?,?,?,?)",candidate.getFullname(), candidate.getEmail(), candidateId); System.out.println("Candidate with id="+candidateId+ " modified successfully!"); } @Override public void deleteCandidate(int candidateId) { jdbcTemplate.update("delete from candidate where id=?", candidateId); System.out.println("Candidate with id="+candidateId+ " deleted successfully!"); } @Override public Candidate find(int candidateId) { Candidate c = null; c = (Candidate) jdbcTemplate.queryForObject("select * from candidate " + "where id=?", new Object[] { candidateId }, new BeanPropertyRowMapper<Candidate>(Candidate. class)); return c; } @Override public List<Candidate> findAll() { List<Candidate> candidates = new ArrayList<>(); candidates = jdbcTemplate.query("select * from candidate", new BeanPropertyRowMapper<Candidate> (Candidate.class)); return candidates; } }
Spring Boot Loader-Klasse:SpringBootJdbcDemoApplication.java
package org.mano.springbootjdbc.demo; import java.util.List; import org.mano.springbootjdbc.demo.dao.CandidateDao; import org.mano.springbootjdbc.demo.model.Candidate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure. SpringBootApplication; @SpringBootApplication public class SpringBootJdbcDemoApplication implements CommandLineRunner { @Autowired private CandidateDao cdao; public static void main(String[] args) { SpringApplication.run(SpringBootJdbcDemoApplication. class, args); } @Override public void run(String... arg0) throws Exception { Candidate c1 = new Candidate(1, "Sachin Tendulkar", "[email protected]", "1234567890"); Candidate c2 = new Candidate(2, "Amit Saha", "[email protected]", "9632587410"); Candidate c3 = new Candidate(3, "Sandip Paul", "[email protected]", "8527419630"); Candidate c4 = new Candidate(4, "Rajib Kakkar", "[email protected]", "9876543210"); Candidate c5 = new Candidate(5, "Rini Simon", "[email protected]", "8624793150"); cdao.addCandidate(c1); cdao.addCandidate(c2); cdao.addCandidate(c3); cdao.addCandidate(c4); cdao.addCandidate(c5); List<Candidate> candidates = cdao.findAll(); for (Candidate candidate : candidates) { System.out.println(candidate); } cdao.deleteCandidate(3); candidates = cdao.findAll(); for (Candidate cc : candidates) { System.out.println(cc); } } }
Application.properties
spring.driverClassName=com.mysql.jdbc.Driver spring.url=jdbc:mysql://localhost:3306/testdb spring.username=root spring.password=secret
Anwendung ausführen
Um die Anwendung auszuführen, klicken Sie im Projekt-Explorer mit der rechten Maustaste auf das Projekt und wählen Sie Ausführen als aus -> Spring Boot-App . Das ist alles.
Schlussfolgerung
Wir haben drei Optionen, um mit der Programmierung relationaler Datenbanken mit Spring zu arbeiten:
- Das altmodische JDBC mit Spring. Das bedeutet, dass das Spring-Framework für alle praktischen Zwecke im Programm verwendet wird, mit Ausnahme der Datenunterstützung von Spring.
- Verwenden von JDBC-Vorlagenklassen. Spring bietet JDBC-Abstraktionsklassen zum Abfragen relationaler Datenbanken; diese sind viel einfacher als die Arbeit mit nativem JDBC-Code.
- Spring bietet auch hervorragende Unterstützung für das ORM-Framework (Object Relational Mapping) und lässt sich gut in eine bekannte Implementierung der JPA-API (Java Persistent Annotation) wie Hibernate integrieren. Es verfügt auch über eine eigene Spring Data JPA-Unterstützung, die zur Laufzeit automatisch eine Repository-Implementierung im laufenden Betrieb generieren kann.
Wenn man sich aus irgendeinem Grund für JDBC entscheidet, ist es besser, die Spring-Vorlagenunterstützung wie JdbcTemplate zu verwenden außer mit ORM.
Referenzen
- Wände, Felsen. Frühling in Aktion 4 , Manning Publications
- Spring 5 API-Dokumentation