גודל Spring JDBC הוא נושא השיעור הזה. בכמעט כל היישומים בעסקים כוללים מסדי נתונים כחלק מהן. ולכן, כשמדובר בפרימורק Java EE, חיבור טוב עם JDBC הוא חשוב מאוד.
Spring JDBC
ה-Spring Framework מספק ניכרות מצוינת עם ממשק ה-JDBC API וספק כיתה יעילה בשם
JdbcTemplate
שבאמצעותה אנו יכולים להימנע מכתיבת קוד מיותר בלוגיקת הפעולות שלנו במסד הנתונים, כמו פתיחה/סגירה של חיבור, ResultSet, PreparedStatement וכו'. נראה תחילה דוגמה פשוטה של אפליקציה ב-Spring JDBC ואז נראה איך כיתת JdbcTemplate יכולה לעזור לנו לכתוב קוד מודולרי בקלות, בלעדיו לדאוג אם המשאבים נסגרים בצורה נכונה או לא. כלי הפיתוח Spring Tool Suite הוא מאוד מועיל לפיתוח אפליקציות ב-Spring, ולכן נשתמש ב-STS כדי ליצור את האפליקציה שלנו ל-Spring JDBC. מבנה הפרויקט הסופי ייראה כמו בדימוי התמונה למטה. ניצור פרויקט פשוט של Spring Maven מתפריט ה-STS, ניתן לבחור שם כלשהו או להישאר עם השם שלי, SpringJDBCExample.
תלות Spring JDBC
לפני הכל עלינו לכלול את Spring JDBC ונהגי המסד נתונים בקובץ pom.xml של הפרויקט של Maven. קובץ ה-pom.xml הסופי שלי ייראה כמו בדוגמה למטה.
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.samples</groupId>
<artifactId>SpringJDBCExample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<!-- Generic properties -->
<java.version>1.6</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Spring -->
<spring-framework.version>4.0.2.RELEASE</spring-framework.version>
<!-- Logging -->
<logback.version>1.0.13</logback.version>
<slf4j.version>1.7.5</slf4j.version>
</properties>
<dependencies>
<!-- Spring and Transactions -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- Spring JDBC Support -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.5</version>
</dependency>
<!-- Logging with SLF4J & LogBack -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
רוב החלק נוצר באופן אוטומטי על ידי STS, אך עדכנתי את גרסת מסגרת העבודה של Spring כך שתשתמש בגרסה העדכנית ביותר, 4.0.2.RELEASE. כמו כן, הוספנו את הפריטים הנדרשים spring-jdbc ו־mysql-connector-java. הפריט הראשון מכיל את מחלקות תמיכת Spring JDBC, והפריט השני הוא דרייבר מסד נתונים. אני משתמש במסד נתונים של MySQL לצורכי הבדיקה שלנו, ולכן הוספתי תלויות jar של MySQL JConnector. אם אתה משתמש במסד נתונים אחר, אז עליך לבצע את השינויים המתאימים בתלויות.
דוגמת Spring JDBC – הגדרת מסד נתונים
בוא ניצור טבלה פשוטה שנשתמש בה ביישום שלנו כדי לבצע פעולות CRUD לדוגמה.
CREATE TABLE `Employee` (
`id` int(11) unsigned NOT NULL,
`name` varchar(20) DEFAULT NULL,
`role` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
דוגמת Spring JDBC – מחלקת מודל
נשתמש בתבנית DAO עבור פעולות JDBC, אז בוא ניצור גוש ג'אווה שיתאר את טבלת העובדים שלנו.
package com.journaldev.spring.jdbc.model;
public class Employee {
private int id;
private String name;
private String role;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
@Override
public String toString(){
return "{ID="+id+",Name="+name+",Role="+role+"}";
}
}
דוגמת Spring JDBC – ממשק ומימוש DAO
עבור התבנית DAO, נתחיל ראשית עם ממשק שמצהיר על כל הפעולות שברצוננו ליישם.
package com.journaldev.spring.jdbc.dao;
import java.util.List;
import com.journaldev.spring.jdbc.model.Employee;
// פעולות CRUD
public interface EmployeeDAO {
//Create
public void save(Employee employee);
//Read
public Employee getById(int id);
//Update
public void update(Employee employee);
//Delete
public void deleteById(int id);
// קבלת הכל
public List getAll();
}
package com.journaldev.spring.jdbc.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import com.journaldev.spring.jdbc.model.Employee;
public class EmployeeDAOImpl implements EmployeeDAO {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void save(Employee employee) {
String query = "insert into Employee (id, name, role) values (?,?,?)";
Connection con = null;
PreparedStatement ps = null;
try{
con = dataSource.getConnection();
ps = con.prepareStatement(query);
ps.setInt(1, employee.getId());
ps.setString(2, employee.getName());
ps.setString(3, employee.getRole());
int out = ps.executeUpdate();
if(out !=0){
System.out.println("Employee saved with id="+employee.getId());
}else System.out.println("Employee save failed with id="+employee.getId());
}catch(SQLException e){
e.printStackTrace();
}finally{
try {
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public Employee getById(int id) {
String query = "select name, role from Employee where id = ?";
Employee emp = null;
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
con = dataSource.getConnection();
ps = con.prepareStatement(query);
ps.setInt(1, id);
rs = ps.executeQuery();
if(rs.next()){
emp = new Employee();
emp.setId(id);
emp.setName(rs.getString("name"));
emp.setRole(rs.getString("role"));
System.out.println("Employee Found::"+emp);
}else{
System.out.println("No Employee found with id="+id);
}
}catch(SQLException e){
e.printStackTrace();
}finally{
try {
rs.close();
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return emp;
}
@Override
public void update(Employee employee) {
String query = "update Employee set name=?, role=? where id=?";
Connection con = null;
PreparedStatement ps = null;
try{
con = dataSource.getConnection();
ps = con.prepareStatement(query);
ps.setString(1, employee.getName());
ps.setString(2, employee.getRole());
ps.setInt(3, employee.getId());
int out = ps.executeUpdate();
if(out !=0){
System.out.println("Employee updated with id="+employee.getId());
}else System.out.println("No Employee found with id="+employee.getId());
}catch(SQLException e){
e.printStackTrace();
}finally{
try {
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public void deleteById(int id) {
String query = "delete from Employee where id=?";
Connection con = null;
PreparedStatement ps = null;
try{
con = dataSource.getConnection();
ps = con.prepareStatement(query);
ps.setInt(1, id);
int out = ps.executeUpdate();
if(out !=0){
System.out.println("Employee deleted with id="+id);
}else System.out.println("No Employee found with id="+id);
}catch(SQLException e){
e.printStackTrace();
}finally{
try {
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public List<Employee> getAll() {
String query = "select id, name, role from Employee";
List<Employee> empList = new ArrayList<Employee>();
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
con = dataSource.getConnection();
ps = con.prepareStatement(query);
rs = ps.executeQuery();
while(rs.next()){
Employee emp = new Employee();
emp.setId(rs.getInt("id"));
emp.setName(rs.getString("name"));
emp.setRole(rs.getString("role"));
empList.add(emp);
}
}catch(SQLException e){
e.printStackTrace();
}finally{
try {
rs.close();
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return empList;
}
}
המימוש של פעולות CRUD הוא פשוט להבנה. אם ברצונך ללמוד עוד על מקור הנתונים, אנא קרא דוגמה על DataSource ב-JDBC.
דוגמה על JDBC ב-Spring – תצורת Bean
אם תביט בכל המחלקות לעיל, כולן משתמשות ב-API הסטנדרטי של JDBC ואין שום הפניה למסגרת Spring JDBC. מחלקות מסגרת Spring JDBC נכנסות לתמונה כאשר אנו יוצרים קובץ תצורה של Spring Bean ומגדירים את ה-beans. ניצור את מקור הנתונים בקובץ התצורה של Spring Bean ונגדיר אותו למחלקת המימוש שלנו. קובץ התצורה שלי נראה כמו בהמשך.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employeeDAO" class="com.journaldev.spring.jdbc.dao.EmployeeDAOImpl">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/TestDB" />
<property name="username" value="pankaj" />
<property name="password" value="pankaj123" />
</bean>
</beans>
ראשית, אנו יוצרים אובייקט DataSource מהמחלקה DriverManagerDataSource
. מחלקה זו מספקת את היישום הבסיסי של DataSource שאנו יכולים להשתמש בו. אנו מעבירים כתובת URL של בסיס הנתונים של MySQL, שם משתמש וסיסמה כמאפיינים לאובייקט DataSource. שוב, האובייקט dataSource מוגדר לאובייקט EmployeeDAOImpl
ואנו מוכנים עם המימוש שלנו של Spring JDBC. המימוש הוא קשר רפואי ואם נרצה להחליף למימוש אחר או להעביר לשרת בסיס נתונים אחר, כל שצריך לעשות הוא לבצע שינויים תואמים בהגדרות האובייקטים. זהו אחד מיתרונות המרכזיים שניתן למצוא במסגרת Spring JDBC.
מחלקת מבחן של Spring JDBC
בואו לכתוב מחלקת מבחן פשוטה כדי לוודא שהכל עובד כמו שצריך.
package com.journaldev.spring.jdbc.main;
import java.util.List;
import java.util.Random;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.journaldev.spring.jdbc.dao.EmployeeDAO;
import com.journaldev.spring.jdbc.model.Employee;
public class SpringMain {
public static void main(String[] args) {
//קבל את ההקשר של Spring
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
//קבל את ה־Bean של EmployeeDAO
EmployeeDAO employeeDAO = ctx.getBean("employeeDAO", EmployeeDAO.class);
//הרץ מספר בדיקות לפעולות CRUD של JDBC
Employee emp = new Employee();
int rand = new Random().nextInt(1000);
emp.setId(rand);
emp.setName("Pankaj");
emp.setRole("Java Developer");
//Create
employeeDAO.save(emp);
//Read
Employee emp1 = employeeDAO.getById(rand);
System.out.println("Employee Retrieved::"+emp1);
//Update
emp.setRole("CEO");
employeeDAO.update(emp);
//קבל הכל
List empList = employeeDAO.getAll();
System.out.println(empList);
//Delete
employeeDAO.deleteById(rand);
//סגור את ההקשר של Spring
ctx.close();
System.out.println("DONE");
}
}
I am using Random Class to generate random number for employee id. When we run above program, we get following output.
Mar 25, 2014 12:54:18 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4b9af9a9: startup date [Tue Mar 25 12:54:18 PDT 2014]; root of context hierarchy
Mar 25, 2014 12:54:18 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring.xml]
Mar 25, 2014 12:54:19 PM org.springframework.jdbc.datasource.DriverManagerDataSource setDriverClassName
INFO: Loaded JDBC driver: com.mysql.jdbc.Driver
Employee saved with id=726
Employee Found::{ID=726,Name=Pankaj,Role=Java Developer}
Employee Retrieved::{ID=726,Name=Pankaj,Role=Java Developer}
Employee updated with id=726
[{ID=726,Name=Pankaj,Role=CEO}]
Employee deleted with id=726
Mar 25, 2014 12:54:19 PM org.springframework.context.support.ClassPathXmlApplicationContext doClose
INFO: Closing org.springframework.context.support.ClassPathXmlApplicationContext@4b9af9a9: startup date [Tue Mar 25 12:54:18 PDT 2014]; root of context hierarchy
DONE
דוגמה ל־Spring JdbcTemplate
אם אתה מביט במחלקת היישום של DAO, יש שם הרבה קוד בולטת שבו אנחנו פותחים וסוגרים חיבור, דפוסים מוכנים ו־ResultSet. זה עשוי לגרום לדליפת משאבים אם מישהו שוכח לסגור את המשאבים בצורה נכונה. אנחנו יכולים להשתמש במחלקת org.springframework.jdbc.core.JdbcTemplate
כדי למנוע את השגיאות הללו. מחלקת Spring JdbcTemplate היא המחלקה המרכזית בחבילת ה־core של Spring JDBC ומספקת הרבה שיטות לביצוע שאילתות ולנתח אוטומטית את ResultSet כדי לקבל את העצם או את רשימת העצמים. כל מה שאנחנו צריכים הוא לספק את הארגומנטים כמערך אובייקטים וליישם ממשקי Callback כמו PreparedStatementSetter
ו־RowMapper
עבור מיפוי ארגומנטים או המרת נתוני ResultSet לאובייקטי סופר. בואו נסתכל על יישום נוסף של EmployeeDAO בו נשתמש במחלקת Spring JdbcTemplate עבור ביצוע שאילתות שונות.
package com.journaldev.spring.jdbc.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import com.journaldev.spring.jdbc.model.Employee;
public class EmployeeDAOJDBCTemplateImpl implements EmployeeDAO {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void save(Employee employee) {
String query = "insert into Employee (id, name, role) values (?,?,?)";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
Object[] args = new Object[] {employee.getId(), employee.getName(), employee.getRole()};
int out = jdbcTemplate.update(query, args);
if(out !=0){
System.out.println("Employee saved with id="+employee.getId());
}else System.out.println("Employee save failed with id="+employee.getId());
}
@Override
public Employee getById(int id) {
String query = "select id, name, role from Employee where id = ?";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
// באמצעות מחלקת RowMapper אנונימית, אנו יכולים ליצור RowMapper נפרד לשימוש חוזר
Employee emp = jdbcTemplate.queryForObject(query, new Object[]{id}, new RowMapper(){
@Override
public Employee mapRow(ResultSet rs, int rowNum)
throws SQLException {
Employee emp = new Employee();
emp.setId(rs.getInt("id"));
emp.setName(rs.getString("name"));
emp.setRole(rs.getString("role"));
return emp;
}});
return emp;
}
@Override
public void update(Employee employee) {
String query = "update Employee set name=?, role=? where id=?";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
Object[] args = new Object[] {employee.getName(), employee.getRole(), employee.getId()};
int out = jdbcTemplate.update(query, args);
if(out !=0){
System.out.println("Employee updated with id="+employee.getId());
}else System.out.println("No Employee found with id="+employee.getId());
}
@Override
public void deleteById(int id) {
String query = "delete from Employee where id=?";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
int out = jdbcTemplate.update(query, id);
if(out !=0){
System.out.println("Employee deleted with id="+id);
}else System.out.println("No Employee found with id="+id);
}
@Override
public List getAll() {
String query = "select id, name, role from Employee";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
List empList = new ArrayList();
List
נקודות חשובות להתייחסות בקוד של Spring JdbcTemplate לעיל הם:
- שימוש במערך אובייקטים כדי למסור ארגומנטים ל־PreparedStatement, אנו יכולים גם להשתמש במימוש של PreparedStatementSetter אבל מעבר אובייקטים נראה פשוט לשימוש.
- אין קוד הקשור לפתיחה וסגירה של חיבורים, דפוסים או ResultSet. הכל מטופל פנימית על ידי מחלקת Spring JdbcTemplate.
- מימוש מחלקת RowMapper אנונימית כדי למפות את נתוני ה־ResultSet לאובייקט Employee בשיטת queryForObject().
- שיטת queryForList() מחזירה רשימת מפות כאשר מפה מכילה את נתוני השורה הממופים עם המפתח כשם העמודה והערך מהשורה במסד הנתונים שתואם לקריטריונים.
כדי להשתמש במימוש של Spring JdbcTemplate, הכל שנצטרך הוא לשנות את מחלקת employeeDAO בקובץ תצורת Spring Bean כפי שמוצג למטה.
<bean id="employeeDAO" class="com.journaldev.spring.jdbc.dao.EmployeeDAOJDBCTemplateImpl">
<property name="dataSource" ref="dataSource" />
</bean>
כאשר תפעיל את מחלקת הראשית, הפלט של המימוש של Spring JdbcTemplate יהיה דומה לזה שנראה למעלה עם המימוש הרגיל של JDBC. זהו הכל לדוגמת Spring JDBC, הוראות מקרה, ניתן להוריד את הפרויקט הדוגמא מהקישור למטה ולשחק איתו כדי ללמוד עוד.
Source:
https://www.digitalocean.com/community/tutorials/spring-jdbc-example