Trong kịch bản ứng dụng thế giới thực, một lượng lớn xử lý được thực hiện tại máy chủ phụ trợ, nơi dữ liệu thực sự được xử lý và lưu giữ trong kho lưu trữ. Ngoài nhiều tính năng nổi bật của Spring, chẳng hạn như DI (Dependency Injection), Aspects và phát triển theo định hướng POJO, Spring còn hỗ trợ tuyệt vời cho việc xử lý dữ liệu. Có nhiều cách khác nhau để viết các ứng dụng cơ sở dữ liệu tốt. Cho đến ngày nay, một số lượng lớn các ứng dụng được viết dựa trên khả năng truy cập dữ liệu JDBC. Bài viết này đề cập cụ thể đến JDBC liên quan đến Spring, sự hỗ trợ của nó cũng như ưu và nhược điểm với các ví dụ và đoạn mã thích hợp.
Tổng quan về JDBC
Một trong những lợi thế lớn nhất của việc vẫn sử dụng JDBC trong thế giới ORM là nó không yêu cầu thông thạo ngôn ngữ truy vấn của khung công tác khác ngoài việc làm việc với dữ liệu ở cấp độ thấp hơn nhiều. Nó cho phép lập trình viên tận dụng các tính năng độc quyền của cơ sở dữ liệu. Nó cũng có những nhược điểm của nó. Thật không may, những nhược điểm thường quá rõ ràng mà chúng không cần phải đề cập đến. Ví dụ:một trong số chúng là mã soạn sẵn . Thuật ngữ mã ghi sẵn về cơ bản có nghĩa là viết đi viết lại cùng một đoạn mã mà không kết hợp bất kỳ giá trị nào trong mã. Điều này thường có thể thấy khi chúng tôi truy vấn dữ liệu từ cơ sở dữ liệu; ví dụ:trong đoạn mã sau, chúng tôi chỉ cần tìm nạp một Người dùng ghi lại từ cơ sở dữ liệu.
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; }
Quan sát thấy rằng, mỗi khi chúng ta cần tương tác với cơ sở dữ liệu, chúng ta phải tạo ba đối tượng — một kết nối ( Kết nối ), câu lệnh ( Câu lệnh chuẩn bị ) và tập kết quả ( ResultSet ). Tất cả những thứ này cũng phải được bao bọc trong try… catch áp đặt khối. Ngay cả việc đóng kết nối cũng phải được bao gồm trong try… catch . Điều này thật lố bịch vì mã yêu cầu thực tế cho hàm ít hơn nhiều. Mã chỉ đơn giản là chứa mã không cần thiết nhưng bắt buộc và phải được lặp lại bất cứ nơi nào chúng ta tương tác với cơ sở dữ liệu. Một sơ đồ mã hóa thông minh có thể giảm bớt sự lộn xộn này, nhưng không thể loại bỏ được vấn đề mã JDBC viết sẵn. Đây không chỉ là vấn đề với JDBC mà còn với JMS, JNDI và REST.
Giải pháp của Spring
Khung công tác Spring đã cung cấp một giải pháp cho mớ hỗn độn này và cung cấp một phương tiện để loại bỏ mã viết sẵn bằng cách sử dụng các lớp mẫu. Các lớp này đóng gói mã soạn sẵn, do đó giúp người lập trình nhẹ nhõm hơn. Điều này có nghĩa là mã soạn sẵn vẫn còn ở đó, chỉ lập trình viên sử dụng một trong các lớp mẫu mới có thể bớt khó khăn khi viết nó. JdbcTemplate do Spring cung cấp là lớp trung tâm của gói lõi JDBC.
Nó đơn giản hóa việc sử dụng JDBC và giúp tránh các lỗi phổ biến. Nó thực thi quy trình làm việc JDBC cốt lõi, để lại mã ứng dụng để cung cấp SQL và trích xuất kết quả. Lớp này thực thi các truy vấn hoặc cập nhật SQL, bắt đầu lặp qua ResultSets và bắt các ngoại lệ JDBC và dịch chúng sang hệ thống phân cấp ngoại lệ chung, nhiều thông tin hơn được xác định trong org.springframework.dao gói hàng.
Chúng tôi chỉ cần triển khai các giao diện gọi lại và cung cấp cho chúng một hợp đồng xác định rõ ràng. Ví dụ: PreparedStatementCreator giao diện gọi lại được sử dụng để tạo một câu lệnh đã chuẩn bị. Công cụ ký kết quả giao diện hoạt động giống như một ResultSet .
Do đó, đoạn mã trước đó có thể được viết lại bằng JdbcTemplate như sau:
@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 là giao diện thường được sử dụng bởi JdbcTemplate để ánh xạ một hàng trên cơ sở các hàng của Tập kết quả . RowMapper các đối tượng là không trạng thái và do đó có thể tái sử dụng. Chúng hoàn hảo để triển khai bất kỳ logic ánh xạ hàng nào. Hãy quan sát rằng, trong mã trước, chúng tôi đã không xử lý các ngoại lệ một cách rõ ràng như chúng tôi đã thực hiện trong mã không sử dụng JdbcTemplate . Việc triển khai RowMapper thực hiện việc triển khai thực tế ánh xạ từng hàng với đối tượng kết quả mà lập trình viên không cần lo lắng về việc xử lý ngoại lệ. Nó sẽ được gọi và xử lý bằng cách gọi JdbcTemplate .
Các trường hợp ngoại lệ
Các ngoại lệ do JDBC cung cấp thường quá áp đặt so với mức cần thiết, ít giá trị. Hệ thống phân cấp ngoại lệ truy cập dữ liệu của Spring được sắp xếp hợp lý và hợp lý hơn về mặt này. Điều này có nghĩa là nó có một tập hợp nhất quán các lớp ngoại lệ trong kho vũ khí của nó trái ngược với một kích thước của JDBC phù hợp với tất cả các ngoại lệ được gọi là SQLException cho tất cả các vấn đề liên quan đến truy cập dữ liệu. Các ngoại lệ truy cập dữ liệu của Spring được bắt nguồn từ DataAccessException lớp. Do đó, chúng ta có thể có cả hai lựa chọn ngoại lệ đã kiểm tra và không kiểm tra đã ăn sâu vào khuôn khổ. Điều này nghe có vẻ thực tế hơn vì thực sự không có giải pháp nào cho nhiều vấn đề xảy ra trong quá trình truy cập dữ liệu thời gian chạy và chúng tôi bắt gặp chúng khi không thể giải quyết tình huống bằng một giải pháp thay thế phù hợp là vô nghĩa.
Cách đơn giản hóa việc truy cập dữ liệu của Spring
Những gì Spring thực sự làm là nó phân biệt phần cố định và phần biến của cơ chế truy cập dữ liệu thành hai nhóm lớp được gọi là lớp mẫu và các lớp gọi lại , tương ứng. Phần cố định của mã đại diện cho phần chiếu lệ của truy cập dữ liệu và phần thay đổi là phương pháp truy cập dữ liệu thay đổi theo yêu cầu thay đổi.
Nói tóm lại, các lớp mẫu xử lý:
- Kiểm soát giao dịch
- Quản lý tài nguyên
- Xử lý ngoại lệ
Và, các lớp gọi lại xử lý:
- Tạo câu lệnh truy vấn
- Ràng buộc tham số
- Điều chỉnh bộ kết quả
Chúng ta có thể chọn một trong nhiều lớp mẫu, tùy theo sự lựa chọn của công nghệ bền bỉ được sử dụng. Ví dụ:đối với JDBC, chúng tôi có thể chọn JdbcTemplate hoặc đối với ORM, chúng tôi có thể chọn JpaTemplate , HibernateTemplate , v.v..
Bây giờ, trong khi kết nối với cơ sở dữ liệu, chúng tôi có ba tùy chọn để định cấu hình nguồn dữ liệu, chẳng hạn như:
- Do trình điều khiển JDBC xác định
- Được JNDI tra cứu
- Tìm nạp từ nhóm kết nối
Một ứng dụng sẵn sàng sản xuất thường sử dụng nhóm kết nối hoặc JNDI. Nguồn dữ liệu do trình điều khiển JDBC xác định cho đến nay là đơn giản nhất, mặc dù nó được sử dụng chủ yếu cho mục đích thử nghiệm. Spring cung cấp ba lớp trong gói org.springframework.jdbc.datasource của thể loại này; họ là:
- DriverManagerDataSource: Triển khai đơn giản JDBC DataSource tiêu chuẩn giao diện, định cấu hình JDBC DriverManager cũ đơn giản thông qua thuộc tính bean và trả về một Kết nối mới từ mọi yêu cầu.
- SingleConnectionDataSource: Trả về cùng một kết nối cho mọi yêu cầu. Loại kết nối này chủ yếu dùng để thử nghiệm.
- SimpleDriverDataSource: Giống như DriverManagerDataSource ngoại trừ việc nó có các vấn đề tải lớp đặc biệt như OSGi; lớp này trực tiếp hoạt động với Trình điều khiển JDBC.
Việc cấu hình các nguồn dữ liệu này cũng tương tự. Chúng ta có thể định cấu hình chúng trong lớp bean hoặc thông qua 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"/>
Các lớp mẫu JDBC
Spring cung cấp một số lớp mẫu để đơn giản hóa việc truy cập dữ liệu với JDBC:
- JdbcTemplate: Đây là lớp cơ bản của gói JDBC lõi org.springframework.jdbc.core cung cấp quyền truy cập đơn giản nhất vào cơ sở dữ liệu thông qua các truy vấn được lập chỉ mục.
- NamedParameterJdbcTemplate: Lớp mẫu này cũng cung cấp một tập hợp các thao tác JDBC cơ bản trong đó các giá trị được liên kết với các tham số được đặt tên thay vì các trình giữ chỗ ‘?’ Truyền thống trong các truy vấn SQL.
Các lớp gọi lại JDBC
Các giao diện chức năng gọi lại JDBC chính được xác định trong org.springframework.jdbc.core là:
- CallableStatementCallback
: Hoạt động trên JDBC CallableStatement. Lệnh gọi lại này được sử dụng nội bộ bởi JdbcTemplate và cho phép thực thi trên một CallableStatement duy nhất chẳng hạn như lệnh gọi SQL đơn hoặc nhiều thực thi với các tham số khác nhau. - PreparedStatementCallback
: Hoạt động trên JDBC PreparedStatement. Lệnh gọi lại này được JdbcTemplate sử dụng nội bộ và cho phép thực hiện nhiều thao tác trên một Câu lệnh chuẩn bị chẳng hạn như lệnh gọi executeUpdate trong SQL đơn hoặc nhiều với các tham số khác nhau. - StatementCallback
: Hoạt động dựa trên Tuyên bố của JDBC . Lệnh gọi lại này cũng được sử dụng nội bộ bởi JdbcTemplate để thực hiện nhiều thao tác trên một Câu lệnh chẳng hạn như một hoặc nhiều lệnh gọi executeUpdate trong SQL.
Ví dụ về JDBC Spring Boot đơn giản
Hãy thử một ví dụ khởi động mùa xuân đơn giản. Dự án khởi động mùa xuân tự động xử lý nhiều sự phức tạp của cấu hình trong đó nhà phát triển được giải tỏa mọi rắc rối sau khi một phần phụ thuộc chính xác được đưa vào tệp Maven pom.xml . Để giữ cho độ dài của bài viết ngắn gọn, chúng tôi sẽ không đưa vào phần giải thích mã. Vui lòng sử dụng các tài liệu tham khảo được cung cấp ở cuối bài viết để có mô tả chi tiết hơn.
Để làm việc trên ví dụ sau, hãy tạo cơ sở dữ liệu và bảng trong MySQl như sau:
Đăng nhập vào cơ sở dữ liệu MySQL và tạo cơ sở dữ liệu và bảng bằng lệnh sau:
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ắt đầu như một dự án khởi động Spring từ Spring Tool Suite (STS) với JDBC và MySQL phụ thuộc. Tệp cấu hình Maven, pom.xml , của dự án như sau:
<?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>
Lớp người mẫu: 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 + "]"; } }
Giao diện đối tượng truy cập dữ liệu: 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(); }
Lớp triển khai đối tượng truy cập dữ liệu: 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; } }
Lớp trình tải khởi động mùa xuân: 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
Chạy ứng dụng
Để chạy ứng dụng, hãy nhấp chuột phải vào dự án trong Project Explorer và chọn Run As -> Ứng dụng khởi động mùa xuân . Đó là tất cả.
Kết luận
Chúng tôi có ba tùy chọn để làm việc với lập trình Cơ sở dữ liệu quan hệ với Spring:
- JDBC kiểu cũ với Spring. Điều này có nghĩa là sử dụng khuôn khổ Spring cho tất cả các mục đích thực tế trong chương trình ngoại trừ hỗ trợ dữ liệu của Spring.
- Sử dụng các lớp mẫu JDBC. Spring cung cấp các lớp trừu tượng JDBC để truy vấn cơ sở dữ liệu quan hệ; những điều này đơn giản hơn nhiều so với làm việc với mã JDBC gốc.
- Spring cũng hỗ trợ tuyệt vời cho khung ORM (Object Relational Mapping) và có thể tích hợp tốt với việc triển khai nổi bật API JPA (Java Persistation Annotation) chẳng hạn như Hibernate. Nó cũng có hỗ trợ Spring Data JPA của riêng mình, có thể tự động tạo quá trình triển khai kho lưu trữ một cách nhanh chóng trong thời gian chạy.
Nếu một người chọn JDBC vì lý do nào đó, tốt hơn nên sử dụng hỗ trợ mẫu Spring như JdbcTemplate ngoài việc sử dụng ORM.
Tài liệu tham khảo
- Tường, Crag. Spring in Action 4 , Manning Publications
- Tài liệu API Spring 5