In uno scenario applicativo reale, un'enorme quantità di elaborazione viene eseguita sul server back-end in cui i dati vengono effettivamente elaborati e mantenuti in un repository. Oltre a molte caratteristiche importanti di Spring, come DI (Dependency Injection), Aspects e sviluppo orientato a POJO, Spring offre un eccellente supporto per la gestione dei dati. Esistono diversi modi per scrivere buone applicazioni di database. Ancora oggi, un gran numero di applicazioni viene scritto sulla base della capacità di accesso ai dati JDBC. Questo articolo tratta specificamente del JDBC in relazione a Spring, del suo supporto e dei pro e contro con esempi e frammenti di codice appropriati.
Panoramica JDBC
Uno dei maggiori vantaggi di utilizzare ancora JDBC nel mondo di ORM è che non richiede la padronanza del linguaggio di query di un altro framework oltre a lavorare con i dati a un livello molto più basso. Consente a un programmatore di sfruttare le funzionalità proprietarie del database. Ha anche i suoi svantaggi. Sfortunatamente, gli svantaggi sono spesso così visibili da non aver bisogno di essere menzionati. Ad esempio, uno di questi è codice boilerplate . Il termine codice boilerplate fondamentalmente significa scrivere lo stesso codice ancora e ancora senza incorporare alcun valore nel codice. Questo in genere può essere visto quando interroghiamo i dati da un database; ad esempio, nel codice seguente, recuperiamo semplicemente un Utente record dal database.
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; }
Osserva che, ogni volta che dobbiamo interagire con il database, dobbiamo creare tre oggetti:una connessione (Connessione ), dichiarazione (PreparedStatement ) e il set di risultati (ResultSet ). Tutti questi devono anche essere racchiusi all'interno dell'imposto try...catch bloccare. Anche la chiusura della connessione deve essere racchiusa all'interno di try...catch . Questo è ridicolo perché il codice effettivo richiesto per la funzione è molto inferiore. Il codice è semplicemente gonfio di codice non necessario ma obbligatorio e deve essere ripetuto ovunque interagiamo con il database. Uno schema di codifica intelligente può ridurre questo pasticcio, ma è impossibile sradicare il problema del codice JDBC standard. Questo non è solo il problema con JDBC, ma anche con JMS, JNDI e REST.
Soluzione di primavera
Il framework Spring ha fornito una soluzione a questo pasticcio e ha fornito un mezzo per eliminare il codice standard utilizzando classi modello. Queste classi incapsulano il codice standard, sollevando così il programmatore. Ciò significa che il codice standard è ancora lì, solo il programmatore che utilizza una delle classi modello è sollevato dal problema di scriverlo. Il Modello Jdbc fornito da Spring è la classe centrale del pacchetto principale JDBC.
Semplifica l'uso di JDBC e aiuta a evitare errori comuni. Esegue il flusso di lavoro JDBC principale, lasciando il codice dell'applicazione per fornire SQL ed estrarre i risultati. Questa classe esegue query o aggiornamenti SQL, avviando l'iterazione su ResultSets e rilevando le eccezioni JDBC e traducendole nella gerarchia di eccezioni generica e più informativa definita in org.springframework.dao pacchetto.
Dobbiamo implementare solo le interfacce di callback e fornire loro un contratto chiaro e definito. Ad esempio, il PreparedStatementCreator l'interfaccia di callback viene utilizzata per creare un'istruzione preparata. L'Estrattore di risultati l'interfaccia agisce come un ResultSet .
Pertanto, il frammento di codice precedente può essere riscritto con JdbcTemplate come segue:
@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; } }
Il RowMapper è un'interfaccia tipicamente utilizzata da JdbcTemplate per mappare una riga per base di righe del ResultSet . Il RowMapper gli oggetti sono stateless e quindi riutilizzabili. Sono perfetti per implementare qualsiasi logica di mappatura delle righe. Osserva che, nel codice precedente, non abbiamo gestito le eccezioni in modo esplicito come abbiamo fatto nel codice che non usa JdbcTemplate . L'implementazione RowMapper esegue l'effettiva implementazione della mappatura di ogni riga sull'oggetto risultato senza che il programmatore debba preoccuparsi della gestione delle eccezioni. Verrà chiamato e gestito chiamando JdbcTemplate .
Le eccezioni
Le eccezioni fornite da JDBC sono spesso troppo imponenti del necessario, con scarso valore. La gerarchia delle eccezioni di accesso ai dati di Spring è più snella e ragionevole a questo riguardo. Ciò significa che ha un insieme coerente di classi di eccezioni nel suo arsenale in contrasto con la dimensione unica di JDBC che si adatta a tutte le eccezioni chiamata SQLException per tutti i problemi relativi all'accesso ai dati. Le eccezioni di accesso ai dati di Spring sono radicate con DataAccessException classe. Pertanto, possiamo avere sia la scelta tra l'eccezione verificata che quella non verificata radicata nel framework. Sembra più pratico perché non ci sono davvero soluzioni a molti dei problemi che si sono verificati durante l'accesso ai dati di runtime ed è inutile prenderli quando non possiamo affrontare la situazione con un'alternativa adeguata.
Il modo in cui la primavera semplifica l'accesso ai dati
Ciò che fa effettivamente Spring è distinguere la parte fissa e variabile del meccanismo di accesso ai dati in due insiemi di classi chiamate classi template e corsi di richiamata , rispettivamente. La parte fissa del codice rappresenta la parte superficiale dell'accesso ai dati e la parte variabile è quel metodo di accesso ai dati che varia a seconda delle mutevoli esigenze.
In breve, le classi di modelli maniglia:
- Controllo delle transazioni
- Gestione delle risorse
- Gestione delle eccezioni
E i corsi di callback maniglia:
- Creazione dell'istruzione di query
- Legatura dei parametri
- Marshalling del set di risultati
Possiamo sceglierne una tra tante classi di template, in base alla scelta della tecnologia persistente utilizzata. Ad esempio, per JDBC possiamo scegliere JdbcTemplate , oppure per ORM possiamo scegliere JpaTemplate , Modello di sospensione , e così via.
Ora, durante la connessione al database, abbiamo tre opzioni per configurare l'origine dati, come ad esempio:
- Definito dal driver JDBC
- Cercato da JNDI
- Recuperato dal pool di connessioni
Un'applicazione pronta per la produzione utilizza in genere un pool di connessioni o JNDI. Le origini dati definite dal driver JDBC sono di gran lunga le più semplici, sebbene siano utilizzate principalmente a scopo di test. Spring offre tre classi nel pacchetto org.springframework.jdbc.datasource di questa categoria; sono:
- DriverManagerDataSource: Semplice implementazione dello standard JDBC DataSource interfaccia, configurando il vecchio JDBC DriverManager tramite le proprietà del bean e restituendo una nuova Connessione da ogni richiesta.
- SingleConnectionDataSource: Restituisce la stessa connessione su ogni richiesta. Questo tipo di connessione è destinato principalmente ai test.
- Sorgente dati driver semplice: Come DriverManagerDataSource tranne per il fatto che ha problemi di caricamento delle classi speciali come OSGi; questa classe funziona direttamente con JDBC Driver.
La configurazione di queste origini dati è simile. Possiamo configurarli in una classe bean o tramite XML.
// 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"/>
Classi modello JDBC
Spring offre un paio di classi modello per semplificare l'accesso ai dati con JDBC:
- JdbcTemplate: Questa è la classe base del pacchetto JDBC principale org.springframework.jdbc.core che fornisce l'accesso più semplice al database tramite query indicizzate.
- NamedParameterJdbcTemplate: Questa classe modello fornisce anche un set di base di operazioni JDBC in cui i valori sono associati a parametri denominati anziché ai tradizionali segnaposto "?" nelle query SQL.
Classi di richiamata JDBC
Le principali interfacce funzionali di callback JDBC definite in org.springframework.jdbc.core sono:
- CallableStatementCallback
: Funziona su JDBC CallableStatement. Questo callback viene utilizzato internamente da JdbcTemplate e consente l'esecuzione su un singolo CallableStatement come SQL singolo o multiplo esegue chiamate con parametri diversi. - PreparedStatementCallback
: Funziona su JDBC PreparedStatement. Questa richiamata viene utilizzata internamente da JdbcTemplate e consente l'esecuzione di più operazioni su un singolo PreparedStatement come una o più chiamate SQL executeUpdate con parametri diversi. - Richiamata dichiarazione
: Funziona sulla Dichiarazione JDBC . Questo callback viene utilizzato anche internamente da JdbcTemplate per eseguire più operazioni su una singola Dichiarazione ad esempio chiamate SQL executeUpdate singole o multiple.
Un semplice esempio JDBC Spring Boot
Proviamo un semplice esempio di avvio primaverile. Un progetto Spring boot gestisce automaticamente molte delle complessità della configurazione in cui uno sviluppatore viene sollevato da tutti i problemi una volta inclusa una dipendenza corretta nel file Maven pom.xml . Per mantenere la lunghezza dell'articolo breve, non includeremo le spiegazioni del codice. Si prega di utilizzare i riferimenti forniti alla fine dell'articolo per una descrizione più dettagliata.
Per lavorare sul seguente esempio, crea un database e una tabella in MySQl come segue:
Accedi al database MySQL e crea un database e una tabella con il seguente comando:
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) );
Inizia come progetto di base di Spring da Spring Tool Suite (STS) con la dipendenza JDBC e MySQL. Il file di configurazione di Maven, pom.xml , del progetto è il seguente:
<?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>
Classe modello: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 + "]"; } }
Interfaccia oggetto di accesso ai dati: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(); }
Classe di implementazione dell'oggetto di accesso ai dati: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; } }
Classe Spring Boot Loader: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); } } }
Proprietà.applicazione
spring.driverClassName=com.mysql.jdbc.Driver spring.url=jdbc:mysql://localhost:3306/testdb spring.username=root spring.password=secret
Esegui l'applicazione
Per eseguire l'applicazione, fare clic con il pulsante destro del progetto in Esplora progetto pannello e seleziona Esegui come -> App Spring Boot . Questo è tutto.
Conclusione
Abbiamo tre opzioni per lavorare con la programmazione di database relazionali con Spring:
- Il vecchio JDBC con Spring. Ciò significa utilizzare il framework Spring per tutti gli scopi pratici del programma, ad eccezione del supporto dati di Spring.
- Utilizzo delle classi modello JDBC. Spring offre classi di astrazione JDBC per interrogare database relazionali; questi sono molto più semplici che lavorare con il codice JDBC nativo.
- Spring ha anche un eccellente supporto per il framework ORM (Object Relational Mapping) e può integrarsi bene con un'importante implementazione dell'API JPA (Java Persistent Annotation) come Hibernate. Ha anche la propria assistenza Spring Data JPA che può generare automaticamente l'implementazione del repository al volo in fase di esecuzione.
Se si opta per JDBC per qualche motivo, è meglio utilizzare il supporto per i modelli Spring come JdbcTemplate diverso dall'utilizzo di ORM.
Riferimenti
- Muri, dirupo. La primavera in azione 4 , Pubblicazioni Manning
- Documentazione API Primavera 5