I ett verkligt applikationsscenario görs en enorm mängd bearbetning på backend-servern där data faktiskt bearbetas och förvaras i ett arkiv. Förutom många framträdande egenskaper hos Spring, såsom DI (Dependency Injection), Aspects och POJO-orienterad utveckling, har Spring utmärkt stöd för datahantering. Det finns olika sätt att skriva bra databasapplikationer. Fortfarande idag skrivs ett stort antal applikationer baserat på JDBC-dataåtkomstförmåga. Den här artikeln handlar specifikt om JDBC i samband med Spring, dess support och för- och nackdelar med lämpliga exempel och kodavsnitt.
JDBC-översikt
En av de största fördelarna med att fortfarande använda JDBC i ORM-världen är att det inte kräver att man behärskar ett annat ramverks frågespråk förutom att arbeta med data på en mycket lägre nivå. Det gör det möjligt för en programmerare att dra nytta av databasens egenutvecklade funktioner. Det har sina nackdelar också. Tyvärr är nackdelarna ofta så synliga att de inte behöver nämnas. Till exempel, en av dem är boilerplate code . Termen boilerplate code innebär i princip att skriva samma kod om och om igen utan att införliva något värde i koden. Detta kan vanligtvis ses när vi frågar efter data från en databas; till exempel, i följande kod hämtar vi helt enkelt en Användare registrera från databasen.
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; }
Observera att varje gång vi behöver interagera med databasen måste vi skapa tre objekt – en anslutning (Anslutning ), uttalande (PreparedStatement ), och resultatuppsättning (Resultatuppsättning ). Alla dessa måste också inkluderas i den pålagda försök...fångst blockera. Även stängningen av anslutningen måste också vara innesluten inom try…catch . Detta är löjligt eftersom den faktiska nödvändiga koden för funktionen är mycket mindre. Koden är helt enkelt uppblåst med onödig men obligatorisk kod och måste upprepas varhelst vi interagerar med databasen. Ett smart kodningsschema kan minska denna röra, men det är omöjligt att utrota problemet med JDBC-kod. Detta är inte bara problemet med JDBC utan även JMS, JNDI och REST.
Vårens lösning
Spring-ramverket gav en lösning på den här röran och gav ett sätt att eliminera boilerplate-kod genom att använda mallklasser. Dessa klasser kapslar in boilerplate-koden, vilket avlastar programmeraren. Det betyder att boilerplate-koden fortfarande finns där, bara programmeraren som använder en av mallklasserna är befriad från besväret med att skriva den. JdbcTemplate som tillhandahålls av Spring är den centrala klassen i JDBC-kärnpaketet.
Det förenklar användningen av JDBC och hjälper till att undvika vanliga fel. Den kör kärnan i JDBC-arbetsflödet och lämnar applikationskod för att tillhandahålla SQL och extrahera resultat. Den här klassen kör SQL-frågor eller uppdateringar, initierar iteration över ResultSets och fångar JDBC-undantag och översätter dem till den generiska, mer informativa undantagshierarkin som definieras i org.springframework.dao paket.
Vi behöver bara implementera call-back-gränssnitten och ge dem ett tydligt, definierat kontrakt. Till exempel, PreparedStatementCreator callback-gränssnitt används för att skapa ett förberett uttalande. ResultsetExtractor gränssnittet fungerar som en Resultatuppsättning .
Därför kan det föregående kodavsnittet skrivas om med JdbcTemplate enligt följande:
@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; } }
RowMapper är ett gränssnitt som vanligtvis används av JdbcTemplate för att mappa en rad per bas av rader i Resultatuppsättningen . RowMapper föremål är statslösa och därför återanvändbara. De är perfekta för att implementera vilken radmappningslogik som helst. Observera att vi i den tidigare koden inte hanterade undantag uttryckligen som vi har gjort i koden som inte använder JdbcTemplate . RowMapper-implementeringen utför den faktiska implementeringen av att mappa varje rad till resultatobjektet utan att programmeraren behöver oroa sig för undantagshantering. Det kommer att anropas och hanteras genom att anropa JdbcTemplate .
Undantagen
Undantagen som tillhandahålls av JDBC är ofta för imponerande än nödvändigt, med lite värde. Springs undantagshierarki för dataåtkomst är mer strömlinjeformad och rimlig i detta avseende. Detta betyder att den har en konsekvent uppsättning undantagsklasser i sin arsenal i motsats till JDBC:s one size fit to all undantag som kallas SQLException för alla problem relaterade till dataåtkomst. Springs undantag för dataåtkomst är rotade med DataAccessException klass. Därför kan vi ha både valet av markerat och okontrollerat undantag inbakat i ramverket. Detta låter mer praktiskt eftersom det egentligen inte finns några lösningar på många av de problem som uppstod under körningsdataåtkomst och det är meningslöst att vi fångar dem när vi inte kan lösa situationen med ett lämpligt alternativ.
Vårens sätt att förenkla dataåtkomst
Vad Spring faktiskt gör är att den särskiljer den fasta och variabla delen av dataåtkomstmekanismen i två uppsättningar klasser som kallas mallklasser och återuppringningskurser , respektive. Den fasta delen av koden representerar den tillfälliga delen av dataåtkomst och den variabla delen är den dataåtkomstmetoden som varierar beroende på det ändrade kravet.
Kort sagt, mallklasserna handtag:
- Transaktionskontroll
- Resurshantering
- Undantagshantering
Och återuppringningskurserna handtag:
- Skapar frågesats
- Parameterbindning
- Resultatuppsättning rangering
Vi kan välja en bland många mallklasser, beroende på valet av beständig teknologi som används. Till exempel, för JDBC kan vi välja JdbcTemplate , eller för ORM kan vi välja JpaTemplate , HibernateTemplate , och så vidare.
När vi nu ansluter till databasen har vi tre alternativ för att konfigurera datakällan, till exempel:
- Definierat av JDBC-drivrutinen
- Slog upp av JNDI
- Hämtad från anslutningspoolen
En produktionsklar applikation använder vanligtvis en anslutningspool eller JNDI. De JDBC-drivrutindefinierade datakällorna är överlägset de enklaste, även om de används mest för teständamål. Spring erbjuder tre klasser i paketet org.springframework.jdbc.datasource av denna kategori; de är:
- DriverManagerDataSource: Enkel implementering av standard JDBC DataSource gränssnitt, konfigurera den vanliga gamla JDBC DriverManager via bönegenskaper och returnerar en ny anslutning från varje begäran.
- SingleConnectionDataSource: Returnerar samma anslutning vid varje begäran. Denna typ av anslutning är i första hand avsedd för testning.
- SimpleDriverDataSource: Samma som DriverManagerDataSource förutom att det har speciella klassladdningsproblem som OSGi; den här klassen fungerar direkt med JDBC-drivrutinen.
Att konfigurera dessa datakällor är liknande. Vi kan konfigurera dem i en bönklass eller via 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"/>
JDBC-mallklasser
Spring erbjuder ett par mallklasser för att förenkla dataåtkomst med JDBC:
- JdbcTemplate: Detta är den grundläggande klassen för JDBC-kärnpaketet org.springframework.jdbc.core som ger den enklaste åtkomsten till databasen genom indexerade frågor.
- NamedParameterJdbcTemplate: Denna mallklass tillhandahåller också en grundläggande uppsättning JDBC-operationer där värdena är bundna med namngivna parametrar snarare än traditionella "?"-platshållare i SQL-frågor.
JDBC-återuppringningsklasser
De viktigaste JDBC callback funktionella gränssnitten definierade i org.springframework.jdbc.core är:
- CallableStatementCallback
: Fungerar på JDBC CallableStatement. Denna återuppringning används internt av JdbcTemplate och tillåter exekvering på ett enda CallableStatement t.ex. enstaka eller flera SQL-köranrop med olika parametrar. - PreparedStatementCallback
: Fungerar på JDBC PreparedStatement. Denna återuppringning används internt av JdbcTemplate och tillåter exekvering av mer än en operation på en enda PreparedStatement som en eller flera SQL executeUpdate-anrop med olika parametrar. - StatementCallback
: Fungerar på JDBC Statement . Denna återuppringning används också internt av JdbcTemplate att utföra mer än en operation på ett enda uttalande som enstaka eller flera SQL executeUpdate-anrop.
Ett enkelt JDBC-exempel med fjäderstövel
Låt oss prova ett enkelt Spring boot-exempel. Ett Spring boot-projekt hanterar automatiskt många av komplexiteten i konfigurationen där en utvecklare är befriad från alla problem när ett korrekt beroende inkluderas i Maven-filen pom.xml . För att hålla artikelns längd kort tar vi inte med kodförklaringar. Använd referenserna i slutet av artikeln för en mer detaljerad beskrivning.
För att arbeta med följande exempel, skapa en databas och en tabell i MySQl enligt följande:
Logga in på MySQL-databasen och skapa en databas och en tabell med följande kommando:
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) );
Börja som ett Spring-startprojekt från Spring Tool Suite (STS) med beroendet JDBC och MySQL. Maven-konfigurationsfilen, pom.xml , för projektet är följande:
<?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>
Modellklass: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 + "]"; } }
Dataåtkomstobjektgränssnitt: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(); }
Dataåtkomstobjektimplementeringsklass: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 Class: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
Kör programmet
För att köra programmet högerklickar du på projektet i Projektutforskaren och välj Kör som -> Spring Boot App . Det är allt.
Slutsats
Vi har tre alternativ för att arbeta med relationsdatabasprogrammering med Spring:
- Den gammaldags JDBC med Spring. Detta innebär att man använder Spring-ramverket för alla praktiska ändamål i programmet förutom Springs datastöd.
- Använda JDBC-mallklasser. Spring erbjuder JDBC abstraktionsklasser för att söka efter relationsdatabaser; dessa är mycket enklare än att arbeta med inbyggd JDBC-kod.
- Spring har också utmärkt stöd för ramverket ORM (Object Relational Mapping) och kan integreras väl med en framträdande implementering av JPA (Java Persistent Annotation) API som Hibernate. Den har också sin egen Spring Data JPA-hjälp som automatiskt kan generera repository-implementering i farten under körning.
Om man väljer JDBC av någon anledning är det bättre att använda Spring-mallstöd som JdbcTemplate annat än att använda ORM.
Referenser
- Väggar, Crag. Vår i aktion 4 , Manning Publications
- Vår 5 API-dokumentation