W rzeczywistym scenariuszu aplikacji, ogromna ilość przetwarzania jest wykonywana na serwerze zaplecza, gdzie dane są faktycznie przetwarzane i utrwalane w repozytorium. Oprócz wielu ważnych funkcji Spring, takich jak DI (Dependency Injection), Aspects i programowanie zorientowane na POJO, Spring ma doskonałe wsparcie dla obsługi danych. Istnieją różne sposoby pisania dobrych aplikacji bazodanowych. Jeszcze dzisiaj wiele aplikacji jest pisanych w oparciu o możliwości dostępu do danych JDBC. Ten artykuł dotyczy w szczególności JDBC w związku ze Springiem, jego obsługą oraz zaletami i wadami z odpowiednimi przykładami i fragmentami kodu.
Omówienie JDBC
Jedną z największych zalet korzystania z JDBC w świecie ORM jest to, że nie wymaga opanowania języka zapytań innego frameworka, poza pracą z danymi na znacznie niższym poziomie. Umożliwia programiście skorzystanie z własności bazy danych. Ma też swoje wady. Niestety wady są często tak widoczne, że nie trzeba o nich wspominać. Na przykład jednym z nich jest kod wzorcowy . Termin kod wzorcowy zasadniczo oznacza pisanie tego samego kodu w kółko bez włączania jakiejkolwiek wartości do kodu. Zwykle można to zaobserwować, gdy wysyłamy zapytanie do bazy danych; na przykład w poniższym kodzie po prostu pobieramy użytkownika rekord z bazy danych.
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; }
Zauważ, że za każdym razem, gdy musimy wejść w interakcję z bazą danych, musimy utworzyć trzy obiekty — połączenie (Połączenie ), oświadczenie (PreparedStatement ) i zestaw wyników (Zestaw wyników ). Wszystko to również musi być zawarte w narzuconym spróbuj…łap blok. Nawet zamknięcie połączenia również musi być ujęte w try…catch . Jest to niedorzeczne, ponieważ rzeczywisty wymagany kod funkcji jest znacznie mniejszy. Kod jest po prostu nadęty niepotrzebnym, ale obowiązkowym kodem i musi być powtarzany wszędzie tam, gdzie wchodzimy w interakcję z bazą danych. Sprytny schemat kodowania może zredukować ten bałagan, ale nie da się wyeliminować problemu z szablonowym kodem JDBC. To nie tylko problem z JDBC, ale także z JMS, JNDI i REST.
Rozwiązanie Springa
Framework Spring zapewnił rozwiązanie tego bałaganu i zapewnił sposób na wyeliminowanie standardowego kodu za pomocą klas szablonów. Klasy te hermetyzują kod wzorcowy, odciążając w ten sposób programistę. Oznacza to, że kod wzorcowy nadal istnieje, jedynie programista korzystający z jednej z klas szablonów jest zwolniony z trudu jej napisania. Szablon Jdbc dostarczana przez Spring jest centralną klasą podstawowego pakietu JDBC.
Upraszcza korzystanie z JDBC i pomaga uniknąć typowych błędów. Wykonuje podstawowy przepływ pracy JDBC, pozostawiając kod aplikacji dostarczający SQL i wyodrębniający wyniki. Ta klasa wykonuje zapytania lub aktualizacje SQL, inicjując iterację zestawów wyników i przechwytując wyjątki JDBC i tłumacząc je na ogólną, bardziej informacyjną hierarchię wyjątków zdefiniowaną w org.springframework.dao pakiet.
Musimy zaimplementować tylko interfejsy oddzwaniania i dać im jasną, określoną umowę. Na przykład PreparedStatementCreator interfejs wywołania zwrotnego służy do tworzenia przygotowanej instrukcji. Ekstraktor zestawu wyników interfejs działa jak zestaw wyników .
Dlatego powyższy fragment kodu może zostać przepisany za pomocą JdbcTemplate w następujący sposób:
@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 jest interfejsem zwykle używanym przez JdbcTemplate mapować jeden wiersz na bazę wierszy ResultSet . RowMapper obiekty są bezstanowe i dlatego nadają się do wielokrotnego użytku. Są idealne do implementacji dowolnej logiki mapowania wierszy. Zauważ, że w poprzednim kodzie nie obsługiwaliśmy wyjątków w sposób jawny, tak jak zrobiliśmy to w kodzie, który nie używa JdbcTemplate . Implementacja RowMapper wykonuje rzeczywistą implementację mapowania każdego wiersza do obiektu wynikowego bez konieczności martwienia się programisty o obsługę wyjątków. Zostanie wywołany i obsłużony przez wywołanie JdbcTemplate .
Wyjątki
Wyjątki zapewniane przez JDBC są często zbyt imponujące, niż to konieczne, i mają niewielką wartość. Hierarchia wyjątków dostępu do danych w Springu jest pod tym względem bardziej uproszczona i rozsądna. Oznacza to, że ma spójny zestaw klas wyjątków w swoim arsenale, w przeciwieństwie do jednego rozmiaru dopasowanego do wszystkich wyjątków JDBC o nazwie SQLException dla wszystkich problemów związanych z dostępem do danych. Wyjątki dostępu do danych Springa są zakorzenione w DataAccessException klasa. W związku z tym możemy mieć zakorzeniony we frameworku wybór wyjątku sprawdzonego i niesprawdzonego. Brzmi to bardziej praktycznie, ponieważ tak naprawdę nie ma rozwiązań wielu problemów, które wystąpiły podczas dostępu do danych w czasie wykonywania i nie ma sensu, gdy wyłapujemy je, gdy nie możemy rozwiązać sytuacji za pomocą odpowiedniej alternatywy.
Sposób Springa na uproszczenie dostępu do danych
To, co właściwie robi Spring, to rozróżnienie stałej i zmiennej części mechanizmu dostępu do danych na dwa zestawy klas zwane klasami szablonów i zajęcia zwrotne , odpowiednio. Stała część kodu reprezentuje pobieżną część dostępu do danych, a zmienna część to metoda dostępu do danych, która zmienia się w zależności od zmieniających się wymagań.
Krótko mówiąc, klasy szablonów uchwyt:
- Kontrola transakcji
- Zarządzanie zasobami
- Obsługa wyjątków
Oraz zajęcia zwrotne uchwyt:
- Tworzenie instrukcji zapytania
- Powiązanie parametrów
- Porządkowanie zestawu wyników
Możemy wybrać jedną z wielu klas szablonów, zgodnie z wyborem zastosowanej technologii trwałej. Na przykład dla JDBC możemy wybrać JdbcTemplate , lub dla ORM możemy wybrać JpaTemplate , Szablon Hibernate i tak dalej.
Teraz podczas łączenia się z bazą danych mamy trzy opcje konfiguracji źródła danych, takie jak:
- Zdefiniowane przez sterownik JDBC
- Wyszukane przez JNDI
- Pobrano z puli połączeń
Aplikacja gotowa do produkcji zazwyczaj korzysta z puli połączeń lub JNDI. Źródła danych zdefiniowane przez sterownik JDBC są zdecydowanie najprostsze, chociaż są używane głównie do celów testowych. Spring oferuje trzy klasy w pakiecie org.springframework.jdbc.datasource tej kategorii; są to:
- Źródło danych sterownika: Prosta implementacja standardowego JDBC DataSource interfejs, konfiguracja zwykłego starego JDBC DriverManager poprzez właściwości fasoli i zwrócenie nowego połączenia z każdego żądania.
- SingleConnectionDataSource: Zwraca to samo połączenie przy każdym żądaniu. Ten typ połączenia jest przeznaczony głównie do testowania.
- SimpleDriverDataSource: To samo co DriverManagerDataSource z wyjątkiem tego, że ma problemy z ładowaniem klas specjalnych, takich jak OSGi; ta klasa działa bezpośrednio ze sterownikiem JDBC.
Konfiguracja tych źródeł danych jest podobna. Możemy je skonfigurować w klasie fasoli lub przez 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"/>
Klasy szablonów JDBC
Spring oferuje kilka klas szablonów, aby uprościć dostęp do danych za pomocą JDBC:
- Szablon Jdbc: To jest podstawowa klasa podstawowego pakietu JDBC org.springframework.jdbc.core który zapewnia najprostszy dostęp do bazy danych poprzez indeksowane zapytania.
- NamedParameterJdbcTemplate: Ta klasa szablonu zapewnia również podstawowy zestaw operacji JDBC, w których wartości są powiązane z nazwanymi parametrami, a nie tradycyjnymi symbolami zastępczymi „?” w zapytaniach SQL.
Klasy wywołań zwrotnych JDBC
Kluczowe funkcjonalne interfejsy wywołania zwrotnego JDBC zdefiniowane w org.springframework.jdbc.core są:
- CallableStatementCallback
: Działa na JDBC CallableStatement. To wywołanie zwrotne jest używane wewnętrznie przez JdbcTemplate i umożliwia wykonanie na jednym CallableStatement takie jak pojedyncze lub wiele wywołań SQL z różnymi parametrami. - PreparedStatementCallback
<: Działa na JDBC PreparedStatement. To wywołanie zwrotne jest wewnętrznie używane przez JdbcTemplate i umożliwia wykonanie więcej niż jednej operacji na jednym PreparedStatement takie jak pojedyncze lub wielokrotne wywołanie instrukcji SQL executeUpdate z różnymi parametrami. - StatementCallback
: Działa na JDBC Oświadczenie . To wywołanie zwrotne jest również używane wewnętrznie przez JdbcTemplate wykonać więcej niż jedną operację na jednym Oświadczeniu takie jak jedno lub wiele wywołań instrukcji SQL executeUpdate.
Prosty przykład JDBC Spring Boot
Wypróbujmy prosty przykład Spring Boot. Projekt rozruchowy Spring automatycznie obsługuje wiele złożoności konfiguracji, w której programista jest zwolniony z wszelkich problemów, gdy poprawna zależność zostanie dołączona do pliku Mavena pom.xml . Aby artykuł był krótki, nie będziemy zamieszczać wyjaśnień dotyczących kodu. Proszę skorzystać z odnośników podanych na końcu artykułu, aby uzyskać bardziej szczegółowy opis.
Aby pracować na poniższym przykładzie, utwórz bazę danych i tabelę w MySQL w następujący sposób:
Zaloguj się do bazy danych MySQL i utwórz bazę danych oraz tabelę za pomocą następującego polecenia:
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) );
Zacznij jako projekt startowy Spring z pakietu Spring Tool Suite (STS) z zależnością JDBC i MySQL. Plik konfiguracyjny Mavena, pom.xml , projektu wygląda następująco:
<?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>
Klasa modelu: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 + "]"; } }
Interfejs obiektu dostępu do danych: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(); }
Klasa implementacji obiektu dostępu do danych: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; } }
Klasa modułu ładującego Spring Boot: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); } } }
Właściwości.aplikacji
spring.driverClassName=com.mysql.jdbc.Driver spring.url=jdbc:mysql://localhost:3306/testdb spring.username=root spring.password=secret
Uruchom aplikację
Aby uruchomić aplikację, kliknij prawym przyciskiem myszy projekt w Eksploratorze projektów okienko i wybierz Uruchom jako -> Aplikacja Spring Boot . To wszystko.
Wniosek
Mamy trzy opcje pracy z programowaniem relacyjnych baz danych w Spring:
- Staromodny JDBC ze Springiem. Oznacza to używanie frameworka Spring do wszystkich praktycznych celów w programie, z wyjątkiem obsługi danych Springa.
- Korzystanie z klas szablonów JDBC. Spring oferuje klasy abstrakcji JDBC do tworzenia zapytań do relacyjnych baz danych; są one znacznie prostsze niż praca z natywnym kodem JDBC.
- Spring ma również doskonałe wsparcie dla frameworka ORM (Object Relational Mapping) i może dobrze integrować się z wybitną implementacją API JPA (Java Persistent Annotation), taką jak Hibernate. Posiada również własną pomoc Spring Data JPA, która może automatycznie generować implementację repozytorium w locie w czasie wykonywania.
Jeśli ktoś z jakiegoś powodu zdecyduje się na JDBC, lepiej użyć wsparcia szablonów Spring, takich jak JdbcTemplate inne niż przy użyciu ORM.
Referencje
- Mury, Urwisko. Wiosna w działaniu 4 , Manning Publikacje
- Dokumentacja API wiosna 5