7. [TD]: پیادهسازی لایه [DAO] از TD با استفاده از API و JDBC
واژگان کلیدی: پایگاههای دادهٔ رابطهای، API، JDBC، SQLException.
بیایید بار دیگر به معماری لایهبندیشدهٔ برنامهمان نگاهی بیندازیم:
![]() |
دادههای مورد نیاز برای انتخابات در پایگاه داده MySQL [dbelections] ذخیره میشوند.
7.1. Support
![]() |
پوشه [support / chap-07] [1] شامل:
- پروژههای اکلیپس برای این فصل، [2];
- اسکریپت SQL برای ایجاد پایگاه دادههای MySQL، [dbelections] و [3]؛
7.2. پایگاه داده [dbelections]
وظیفه: ایجاد پایگاههای داده MySQL و [dblelections] با پیروی از رویهای که در بخش 6.4.2 تشریح شده است.
پایگاه داده [dbelections] یک پایگاه داده MySQL است که شامل دو جدول میباشد:
![]() |
جدول [conf] شامل جزئیات پیکربندی انتخابات است:
![]() |
- [id]: کلید اصلی با افزایش خودکار؛
- [version]: شماره نسخه رکورد – در اینجا میتوان آن را نادیده گرفت؛
- [sap]: تعداد صندلیهای قابل پر شدن؛
- [seuilelectoral]: آستانهای که هر فهرستی که از آن کمتر باشد، حذف میشود؛
محتوای آن به شرح زیر است:
![]() |
جدول [listes] شامل فهرست نامزدها برای انتخابات است:
![]() |
- [id]: کلید اصلی با افزایش خودکار؛
- [version]: شماره نسخه رکورد – در اینجا میتوان آن را نادیده گرفت؛
- [nom]: نام فهرست؛
- [voix]: ورودی لیست – تنها پس از وارد کردن دادهها در لایه [présentation] مشخص میشود؛
- [sieges]: تعداد کرسیهای کسبشده – تنها پس از محاسبه لایه [métier] مشخص میشود؛
- [elimine]: 1 اگر لیست حذف شود، 0 در غیر این صورت – تنها پس از محاسبه لایه [métier] مشخص میشود؛
محتویات جدول [listes] به شرح زیر است:
![]() |
اسکریپت SQL برای تولید پایگاه داده [dbelections] با نام [dbelections.sql] فراخوانی میشود و روی سرور قرار دارد. کد آن به شرح زیر است:
--phpMyAdmin SQL Dump
-- نسخهٔ ۴.۰.۴
-- http://www.phpmyadmin.net
--
-- کلاینت: localhost
-- تولید شده در: چهارشنبه ۱۱ مارس ۲۰۱۵ ساعت ۱۲:۲۰
-- نسخهٔ سرور: 5.6.12-log
-- PHP نسخه: 5.4.12
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
--
--پایگاه داده: `dbelections`
--
CREATE DATABASE IF NOT EXISTS `dbelections` DEFAULT CHARACTER SET utf8 COLLATE utf8_swedish_ci;
USE `dbelections`;
-- --------------------------------------------------------
--
--ساختار جدول `conf`
--
CREATE TABLE IF NOT EXISTS `conf` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`version` int(11) NOT NULL DEFAULT '1',
`sap` tinyint(4) NOT NULL,
`seuilelectoral` double NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_swedish_ci AUTO_INCREMENT=2 ;
--
-- محتویات جدول `conf`
--
INSERT INTO `conf` (`id`, `version`, `sap`, `seuilelectoral`) VALUES
(1, 1, 6, 0.05);
-- --------------------------------------------------------
--
--ساختار جدول `listes`
--
CREATE TABLE IF NOT EXISTS `listes` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`version` int(11) NOT NULL DEFAULT '1',
`nom` varchar(20) COLLATE utf8_swedish_ci NOT NULL,
`voix` int(11) NOT NULL,
`sieges` int(11) NOT NULL,
`elimine` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `nom` (`nom`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_swedish_ci AUTO_INCREMENT=8 ;
--
-- محتویات جدول `listes`
--
INSERT INTO `listes` (`id`, `version`, `nom`, `voix`, `sieges`, `elimine`) VALUES
(1, 21, 'A', 10, 1, 0),
(2, 22, 'B', 20, 2, 0),
(3, 21, 'C', 30, 3, 0),
(4, 13, 'D', 40, 3, 0),
(5, 17, 'E', 50, 6, 0),
(6, 18, 'F', 60, 1, 0),
(7, 19, 'G', 70, 2, 0);
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
7.3. پروژه اکلیپس
![]() |
پروژه اکلیپس برای لایه [DAO] به شرح زیر خواهد بود:
![]() |
- پکیج [elections.dao.entities] حاوی اشیایی است که توسط لایه [DAO] پردازش میشوند؛
- پکیج [elections.dao.service] شامل پیادهسازی لایه [DAO] است؛
- پکیج [elections.dao.config] حاوی پیکربندی برای لایه [DAO] است
- پکیج [elections.dao.junit] شامل یک کلاس تست JUnit از پروژه است؛
- پکیج [elections.dao.console] شامل یک کلاس تست قابل اجرا است؛
7.4. پیکربندی پروژه Maven
![]() |
فایل [pom.xml] که پروژه Maven را پیکربندی میکند، به شرح زیر است:
<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>istia.st.elections</groupId>
<artifactId>elections-dao-jdbc-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- اسپرینگ -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- Tomcat JDBC -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
<!--کتابخانه jSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Logging -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<!-- پلاگینها -->
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>config.AppConfig</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!--برای نصب محصول پروژه در مخزن محلی Maven -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
این فایل مشابه فایلی است که در بخش 6.5.1 توضیح داده شده است. تغییرات زیر اعمال شده است:
- خطوط ۸–۱۲: یک پروژهٔ والد Maven تعریف شده است. پروژه [spring-boot-starter-parent] (خط ۱۰) تعداد بسیار زیادی وابستگی به همراه نسخههایشان را تعریف میکند. هنگامی که یکی از این موارد استفاده میشود (خطوط ۱۹–۵۷)، نیازی به مشخص کردن نسخه نیست، زیرا این نسخه در پروژه والد Maven تعریف شده است؛
- خطوط ۴۰–۵۱: وابستگیهای مورد نیاز برای کلاس تست پروژه. این وابستگیها دارای ویژگی [<scope>test</scope>] هستند، که به این معنی است که فقط برای کلاسهای موجود در پوشه [src / test / java] مورد نیاز هستند. این وابستگیها در آرشیو نهایی پروژه گنجانده نخواهند شد؛
- خطوط ۵۳–۵۶: کتابخانه [spring-boot-starter-logging] توسط Spring برای ثبت لاگ روی صفحه نمایش استفاده خواهد شد؛
- خطوط 14–17: ویژگیهای پیکربندی Maven:
- خط ۱۵: مشخص میکند که فایلهای منبع در UTF-8 نوشته شدهاند؛
- خط ۱۶: مشخص میکند که کامپایلری که باید استفاده شود باید نسخه ۱.۸ باشد؛
7.5. اشیاء در لایه [DAO]
![]() |
- [ElectionsConfig] مدل شیء مرتبط با یک سطر در جدول [CONF] است؛
- [ListeElectorale] مدل شیء مرتبط با یک سطر در جدول [LISTES] است؛
- [AbstactEntity] کلاس والد دو کلاس قبلی است. این کلاس فیلدهای مشترک [id, version] را که در هر دو کلاس وجود دارند، استخراج میکند؛
- [ElectionsException] یک کلاس استثنا است؛
7.5.1. کلاس [ElectionsException]
![]() |
کلاس [ElectionsException] در بخش 4.3 توصیف شده است. کد آن را در زیر بازنشر میکنیم:
package ...;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
//کلاس استثنا برای برنامه انتخابات
// استثناء گرفته نشده است
public class ElectionsException extends RuntimeException implements Serializable {
// سریال ID
private static final long serialVersionUID = 1L;
// fields محلی
private int code;
private List<String> erreurs;
// سازندهها
public ElectionsException() {
super();
}
public ElectionsException(int code, Throwable e) {
// والد
super(e);
// محلی
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message, Throwable e) {
// والد
super(message,e);
// محلی
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message) {
// والد
super(message);
// محلی
this.code = code;
List<String> erreurs = new ArrayList<>();
erreurs.add(message);
this.erreurs = erreurs;
}
public ElectionsException(int code, List<String> erreurs) {
// parent
super();
// محلی
this.code = code;
this.erreurs = erreurs;
}
// فهرست پیامهای خطا برای یک استثنا
private List<String> getErreursForException(Throwable th) {
// فهرست پیامهای خطا برای استثنا را بازیابی میکند
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// پیام را تنها در صورتی بازیابی میکند که != null و رشتهی خالی نباشد
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// علت بعدی
cause = cause.getCause();
}
return erreurs;
}
// گیرندهها و تنظیمکنندهها
...
}
یک شیء [ElectionsException] با دو اطلاعات مشخص میشود:
- خط ۱۶: یک کد خطا؛
- خط ۱۷: فهرستی از پیامهای خطا مرتبط با ردیابی پشته (stack trace) استثناءهای رخداده؛
- کلاس دارای ۵ سازنده است (خطوط ۲۰، ۲۴، ۳۲، ۴۰، ۵۰)؛
- خطوط 59–76: متد [getErreursForException] برای بازیابی پیامهای خطا از استک استثنا استفاده میشود؛
7.5.2. کلاس [AbstractEntity]
![]() |
کلاس [AbstractEntity] به شرح زیر است:
package elections.dao.entities;
import java.io.Serializable;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public abstract class AbstractEntity implements Serializable {
private static final long serialVersionUID = 1L;
// fields
protected Long id;
protected Long version;
// سازندهها
public AbstractEntity() {
}
public AbstractEntity(Long id, Long version) {
this.id = id;
this.version = version;
}
// امضا
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
// گیرندهها و تنظیمکنندهها
...
}
این کلاس فیلدهای [ID, NOM] را از سطرهای جدولهای [CONF] و [LISTES] (سطرهای 8–9) ذخیره میکند.
- خط ۸: کلاس دارای ویژگی [Abstract] است که نشان میدهد نمیتوان آن را نمونهسازی کرد. تنها میتوان از آن ارث برد؛
- خطوط 26–33: امضای شیء jSON است؛
- خط ۲۸: رشته jSON از [this] بازگردانده میشود. اگر در زمان اجرا، [this] نشاندهنده شیای مشتقشده از [AbstactEntity] باشد، رشته jSON از شی مشتقشده بهدست میآید. بنابراین کلاسهای مشتق نیازی به تعریف متد [toString] نخواهند داشت. متد موجود در کلاس والد کافی است؛
7.5.3. کلاس [ElectionsConfig]
![]() |
کلاس [ElectionsConfig] به شرح زیر است:
package elections.dao.entities;
public class ElectionsConfig extends AbstractEntity {
private static final long serialVersionUID = 1L;
// fields
private int nbSiegesAPourvoir;
private double seuilElectoral;
// سازندهها
public ElectionsConfig() {
}
public ElectionsConfig(Long id, Long version, int nbSiegesAPourvoir, double seuilElectoral) {
// پدر
super(id, version);
// fields محلی
this.nbSiegesAPourvoir = nbSiegesAPourvoir;
this.seuilElectoral = seuilElectoral;
}
// گیرندهها و تنظیمکنندهها
...
}
- خط ۴: کلاس از کلاس [AbstractEntity] ارث میبرد؛
- خطوط ۸–۹: اطلاعات را از ستونهای [SAP, SEUILELECTORAL] جدول [CONF] ذخیره کنید؛
7.5.4. کلاس [ListeElectorale]
![]() |
کلاس [ListeElectorale] به شرح زیر است:
package elections.dao.entities;
public class ListeElectorale extends AbstractEntity {
// fields
private String nom;
private int voix;
private int sieges;
private boolean elimine;
// سازندهها
public ListeElectorale() {
}
public ListeElectorale(String nom, int voix, int sieges, boolean elimine) {
// پدر
super();
// fields محلی
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
public ListeElectorale(Long id, Long version, String nom, int voix, int sieges, boolean elimine) {
// والد
super(id, version);
// fields محلی
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
// متدهای خصوصی
private void initNom(String nom) {
this.nom = nom.trim();
if ("".equals(nom)) {
throw new ElectionsException(10, "Le nom ne peut être vide");
}
}
private void initVoix(int voix) {
this.voix = voix;
if (voix < 0) {
throw new ElectionsException(11, "Le nombre de voix ne peut être <0");
}
}
private void initSieges(int sieges) {
this.sieges = sieges;
if (sieges < 0) {
throw new ElectionsException(12, "Le nombre de sièges ne peut être <0");
}
}
// گیرنده و تنظیمکننده
public String getNom() {
return nom;
}
public void setNom(String nom) {
initNom(nom);
}
public int getVoix() {
return voix;
}
public void setVoix(int voix) {
initVoix(voix);
}
public int getSieges() {
return sieges;
}
public void setSieges(int sieges) {
initSieges(sieges);
}
public boolean isElimine() {
return elimine;
}
public void setElimine(boolean elimine) {
this.elimine = elimine;
}
}
- خط ۳: کلاس از کلاس [AbstractEntity] ارث میبرد؛
- خطوط ۶–۹: کلاس ستونهای [NOM, VOIX, SIEGES, ELIMINE] را از جدول [LISTES] ذخیره میکند؛
7.6. پیکربندی Spring برای لایه [DAO]
![]() |
![]() |
کلاس [AppConfig] یک کلاس پیکربندی Spring است که دسترسی به پایگاه داده را به شرح زیر پیکربندی میکند:
package elections.dao.config;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan(basePackages = { "elections.dao.service" })
@EnableCaching
public class AppConfig {
// ثوابت
public final static String URL = "jdbc:mysql://localhost:3306/dbelections";
public final static String USER = "root";
public final static String PASSWD = "";
public final static String DRIVER_CLASSNAME = "com.mysql.jdbc.Driver";
public final static String SELECT_LISTES = "SELECT ID, VERSION, NOM, VOIX, SIEGES, ELIMINE FROM LISTES";
public final static String SELECT_CONF = "SELECT ID, VERSION, SAP, SEUILELECTORAL, SAP FROM CONF";
public final static String UPDATE_LISTES = "UPDATE LISTES SET VOIX=?, SIEGES=?, ELIMINE=? WHERE ID=?";
@Bean
public DataSource dataSource() {
// منبع داده TomcatJdbc
DataSource dataSource = new DataSource();
// دسترسی پیکربندی JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// یک اتصال در ابتدا باز
dataSource.setInitialSize(1);
// نتیجه
return dataSource;
}
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- خطوط ۲۵–۳۸: دسترسی به BD از طریق یک منبع داده [tomcat-jdbc] خواهد بود. این نوع منبع داده در بخش ۶.۵ استفاده و توضیح داده شده است؛
- خطوط 17–23: مجموعهای از ثابتهای ایستا که برای همه کلاسهای پروژه قابل دسترسی هستند؛
- خط ۱۳: آناوتیشن [@Configuration] کلاس [AppConfig] را بهعنوان یک کلاس پیکربندی Spring تعیین میکند؛
- خط ۱۱: آناوتیشن [@ComponentScan] بستههایی را که اشیاء Spring در آنها یافت میشوند، مشخص میکند. در اینجا، ما قصد داریم یک شیء Spring را در بسته [dao] تعریف کنیم. آناوتیشن [@ComponentScan] کلاس را به یک کلاس پیکربندی تبدیل میکند، که ما را از نیاز به درج آناوتیشن [@Configuration] بینیاز میسازد؛
- خط ۱۲ مدیریت کش را فعال میکند. اصل کار به این صورت است:
- ما آنوتیشن [@Cacheable('nom_du_cache')] را به متد M اعمال میکنیم. 'cache_name' نامی است که در خط ۴۱ استفاده شده است؛
- وقتی متد M برای اولین بار فراخوانی میشود، نتایج آن بازگردانده شده و همچنین در کشی که توسط آنوتیشن نامگذاری شده است ذخیره میشود؛
- وقتی متد M بعداً با همان پارامترهای بار اول فراخوانی میشود، اجرا نمیشود و Spring به سادگی مقادیر کششده را بازمیگرداند؛
7.7. پیکربندی لاگ
![]() |
کتابخانههای لاگگیری توسط وابستگی زیر در [pom.xml] تعریف شدهاند:
<!-- لگگیری Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
این وابستگی شامل کتابخانههای زیر است:
![]() |
کتابخانه [logback] مسئول رسیدگی به لاگها است. این کتابخانه توسط دو فایل پیکربندی میشود:
- [logback.xml] برای شاخه اصلی کد؛
- [logback-test.xml] برای شاخهٔ تست کد. اگر این فایل وجود نداشته باشد، فایل قبلی به جای آن استفاده میشود؛
هر دو فایل باید در دایرکتوری [Classpath] پروژه قرار گیرند. به همین دلیل، آنها در پوشه زیر قرار داده شدهاند:
- [src / main / ressources] برای شاخه اصلی کد؛
- [src / test / ressources] برای شاخهٔ آزمایشی کد؛
محتویات این فایلها در اینجا یکسان است:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- کدگذارها بهطور پیشفرض نوع اختصاص داده میشوند
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!--کنترل سطح لاگ -->
<root level="info"> <!-- info, debug, warn -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
همه چیز در خط ۱۲ اتفاق میافتد، جایی که سطح جزئیات مورد نظر تنظیم میشود:
- [debug]: دقیقترین سطح؛
- [off]: بدون گزارش؛
- [info]: سطح ثبت استاندارد؛
- [warn]: مشابه [info] به علاوه پیامهای هشدار. این پیامها نشاندهنده یک خطای احتمالی هستند؛
به محض بروز خطا در حین اجرا، به حالت [debug] سوئیچ کنید.
7.8. پیادهسازی لایه [DAO]
![]() |
![]() |
رابط [IDao] لایه [DAO] به شرح زیر است:
package istia.st.elections.webapi.client.dao;
import istia.st.elections.webapi.client.entities.ElectionsConfig;
import istia.st.elections.webapi.client.entities.ListeElectorale;
public interface IDao {
// پیکربندی انتخابات
public ElectionsConfig getElectionsConfig();
// فهرستهای رقیب
public ListeElectorale[] getListesElectorales();
// ذخیره نتایج انتخابات
public void setListesElectorales(ListeElectorale[] listesElectorales);
}
اسکلت کلاس [ElectionsDaoJdbc] که لایه [dao] را با یک پایگاه داده پیادهسازی میکند، به شرح زیر خواهد بود:
package elections.dao.service;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import elections.dao.entities.ElectionsConfig;
import elections.dao.entities.ElectionsException;
import elections.dao.entities.ListeElectorale;
@Component
@SuppressWarnings("unused")
public class ElectionDaoJdbc implements IElectionsDao {
@Autowired
private DataSource dataSource;
@Cacheable("electionsConfig")
//بازیابی پیکربندی انتخابات
public ElectionsConfig getElectionsConfig() {
throw new RuntimeException("[getElectionsConfig] not yet implemented");
}
//بازیابی فهرست نامزدها
public ListeElectorale[] getListesElectorales() {
throw new RuntimeException("[getListesElectorales] not yet implemented");
}
//اصلاح فهرستها [voix, sieges, elimine]
public void setListesElectorales(ListeElectorale[] listesElectorales) {
throw new RuntimeException("[setListesElectorales] not yet implemented");
}
// -------------------- متدهای خصوصی
// پردازش بلوک 'finally'
private ElectionsException doFinally(int code, ResultSet rs, PreparedStatement ps, Connection connexion) {
// بستن ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
//بستن [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// بستن اتصال
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// یک [ElectionsException] را بازمیگرداند
return new ElectionsException(code, e3);
}
}
// بدون استثنا
return null;
}
// پردازش خطا
private ElectionsException doCatchException(int code1, int code2, Connection connexion, Throwable th) {
//تولید میکند [ElectionsException]
ElectionsException ex1 = new ElectionsException(code1, th);
// لغو تراکنش
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
}
// استثناء بازگردانده میشود
return ex1;
}
}
- خط ۲۴: anotasyon [@Cacheable] یک anotasyon Spring است که به سیستم دستور میدهد نتایج متد [getElectionsConfig] را در حافظه پنهان ذخیره کند. این کار در اینجا ممکن است زیرا محتویات جدول [CONF] هرگز تغییر نمیکنند. اعمال این آنوتیشن بر روی متد [getListesElectorales] امکانپذیر نخواهد بود، زیرا محتویات جدول [LISTES] در طول زمان تغییر میکنند؛
- متدهای [doCatchException] و [doFinally] نوع [ElectionsException] را بازمیگردانند. متد [doFinally] اگر منابع بدون خطا آزاد شده باشند، یک نشانگر از نوع null برمیگرداند؛
وظیفه: کلاس [ElectionDaoJdbc] را بنویسید، با الهام از کلاس [IntroJdbc02] که در بخش 6.5.2 مورد بحث قرار گرفته است.
7.9. کلاس آزمایشی [Main]
![]() |
![]() |
کلاس [Main] به شرح زیر است:
package elections.dao.console;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import elections.dao.config.AppConfig;
import elections.dao.entities.ElectionsConfig;
import elections.dao.entities.ElectionsException;
import elections.dao.entities.ListeElectorale;
import elections.dao.service.IElectionsDao;
public class Main {
//منبع داده
private static IElectionsDao dao;
public static void main(String[] args) {
//بازیابی ارجاع لایه [DAO] پس از ایجاد نمونهٔ کنتکست Spring
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class)) {
//بازیابی منبع داده
dao = ctx.getBean(IElectionsDao.class);
} catch (Exception e) {
System.out.println("Les erreurs suivantes se sont produites lors de l'initialisation du contexte Spring -------");
for (String erreur : getErreursForThrowable(e)) {
System.out.println(erreur);
}
// پایان
return;
}
// خواندن از BD
ElectionsConfig electionsConfig = null;
ListeElectorale[] listes;
try {
// محتوای دو جدول
electionsConfig = dao.getElectionsConfig();
listes = dao.getListesElectorales();
} catch (ElectionsException e) {
System.out.println("Les erreurs suivantes se sont produites lors de la lecture des tables ----------");
for (String erreur : e.getErreurs()) {
System.out.println(erreur);
}
//پایان
return;
}
// همه چیز خوب پیش رفت – نمایش
System.out.println(String.format("Nombre de sièges à pourvoir : %d", electionsConfig.getNbSiegesAPourvoir()));
System.out.println(String.format("Seuil électoral : %5.2f", electionsConfig.getSeuilElectoral()));
System.out.println("Listes candidates----------------");
for (ListeElectorale liste : listes) {
System.out.println(liste);
}
}
// متدهای خصوصی ------------------
private static List<String> getErreursForThrowable(Throwable th) {
// بازیابی لیست پیامهای خطا از استثنا
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// پیام را تنها در صورتی بازیابی میکند که != null و خالی نباشد
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// علت بعدی
cause = cause.getCause();
}
return erreurs;
}
}
- خطوط ۲۱–۳۱: بازیابی یک مرجع به لایه [DAO]؛
- خط ۲۱: از نحوی به نام try_with_resources استفاده میشود. نحوی آن به شرح زیر است:
try (T ressource=expression) {
//بهرهبرداری از منبع
...
}
- (ادامه)
- نوع T در خط 1 باید رابط [java.lang.AutoCloseable] را پیادهسازی کند؛
- پس از خروج از بلوک شامل خطوط ۱ تا ۴، منبع از نوع [java.lang.AutoCloseable] به طور خودکار آزاد میشود، صرفنظر از اینکه آیا خطایی رخ داده باشد یا خیر. کسانی که با زبان C# آشنا هستند، این را به عنوان همتای نحوی و کاربردی عبارت using (T resource = expression) خواهند شناخت؛
- خطوط ۴۰–۴۹: استفاده از لایه [DAO] برای بازیابی محتویات جداول [CONF] و [LISTES]؛
- خطوط ۴۱–۴۷: کش [electionsconfig] آزمایش میشود. این در دو مکان تعریف شده است:
- در کلاس [ElectionsDaoJdbc]:
@Cacheable("electionsConfig")
public ElectionsConfig getElectionsConfig() {
- (ادامه)
- در کلاس پیکربندی [AppConfig]:
@EnableCaching
public class AppConfig {
...
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- خط ۵۹: بستن کانکست Spring؛
- خطوط ۶۲–۶۷: نمایش اطلاعات بهدستآمده؛
نتایج بهدستآمده با لایه پیادهسازیشده [DAO] به شرح زیر است:
- خطوط ۲–۴: نشاندهنده تأثیر کش:
- درخواست ۱ معادل ۸۰ میلیثانیه زمان میبرد؛
- پرسوجوی ۲ یک میلیثانیه طول میکشد؛
وقتی پایگاه داده غیرفعال باشد، نتایج زیر به دست میآید:
7.10. آزمایشهای JUnit از کلاس [ElectionsDaoJdbc]
![]() |
![]() |
کلاس [Test01] جانشین کلاس تست [JUnit] است:
package elections.dao.junit;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import elections.dao.config.AppConfig;
import elections.dao.entities.ElectionsConfig;
import elections.dao.entities.ListeElectorale;
import elections.dao.service.IElectionsDao;
@SpringApplicationConfiguration(classes = AppConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Test01 {
// [DAO] لایه
@Autowired
private IElectionsDao electionsDao;
@Before
public void init() {
// جدول پاک میشود [LISTES]
// فهرستهای رقیب
ListeElectorale[] listes = electionsDao.getListesElectorales();
// آراء و کرسیها را روی 0 تنظیم کرده و مقدار 'eliminated' را روی false قرار دهید
int voix = 0;
int sièges = 0;
boolean elimine = false;
for (ListeElectorale liste : listes) {
liste.setVoix(voix);
liste.setSieges(sièges);
liste.setElimine(elimine);
}
// ما این دادهها را با استفاده از لایه [dao] پایدار میکنیم
electionsDao.setListesElectorales(listes);
}
@Test
public void testElections01() {
...
}
}
- خط ۱۷: انوتهیشن [RunWith]، که یک انوتهیشن از [JUnit] (خط ۶) است، یکپارچهسازی با Spring را از طریق کلاس [SpringJUnit4ClassRunner] فراهم میکند؛
- خط 16: آناوتیشن [SpringApplicationConfiguration] یک آناوتیشن [Spring] (خط 8) است که برای مشخص کردن کلاسهای پیکربندی تست JUnit استفاده میشود. در اینجا، ما کلاس [AppConfig] را که برای پیکربندی پروژه استفاده میشود، مشخص میکنیم. سپس به تمام اشیاء Spring تعریفشده توسط این کلاس پیکربندی دسترسی خواهیم داشت. به این ترتیب است که میتوانیم در خطوط ۲۱–۲۲، مرجعی به لایه [DAO] که قرار است تست شود، تزریق کنیم؛
- خط ۲۵: آناوتیشن [Before] متدی را مشخص میکند که باید قبل از هر تست اجرا شود؛
- خطوط 26–41: متد [init] آراء و کرسیها را در لیستهای جدول [LISTES] به صفر و مقدار بولین [elimine] را به [false] تنظیم میکند؛
تنها روش آزمون به شرح زیر است:
@Test
public void testElections01() {
System.out.println("testElections01-------------------------------------");
//بازیابی پیکربندی انتخابات
ElectionsConfig electionsConfig = electionsDao.getElectionsConfig();
int nbSiegesAPourvoir = electionsConfig.getNbSiegesAPourvoir();
double seuilElectoral = electionsConfig.getSeuilElectoral();
Assert.assertEquals(6, nbSiegesAPourvoir);
Assert.assertEquals(0.05, seuilElectoral, 1E-6);
// فهرستها در انتخابات
ListeElectorale[] listes = electionsDao.getListesElectorales();
//نمایش مقادیر خواندهشده
System.out.println("Nombre de sièges à pourvoir : " + nbSiegesAPourvoir);
System.out.println("Seuil électoral : " + seuilElectoral);
System.out.println("Listes en compétition ---------------------");
for (int i = 0; i < listes.length; i++) {
System.out.println(listes[i]);
}
//آراء و کرسیها به فهرستها تخصیص داده میشوند
int voix = 0;
int sièges = 0;
boolean elimine = false;
for (ListeElectorale liste : listes) {
liste.setVoix(voix);
liste.setSieges(sièges);
liste.setElimine(elimine);
voix += 10;
sièges += 1;
elimine = !elimine;
}
//این دادهها با استفاده از لایه [dao] پایدارسازی میشوند
electionsDao.setListesElectorales(listes);
//دادهها دوباره خوانده میشوند
ListeElectorale[] listesElectorales2 = electionsDao.getListesElectorales();
//دادههای خواندهشده تأیید میشوند
Assert.assertEquals(7, listesElectorales2.length);
voix = 0;
sièges = 0;
elimine = false;
for (ListeElectorale liste : listesElectorales2) {
Assert.assertEquals(voix, liste.getVoix());
Assert.assertEquals(sièges, liste.getSieges());
Assert.assertEquals(elimine, liste.isElimine());
voix += 10;
sièges += 1;
elimine = !elimine;
}
System.out.println("Listes en compétition ---------------------");
for (int i = 0; i < listes.length; i++) {
System.out.println(listes[i]);
}
}
- خطوط ۵–۹: اطمینان حاصل کنید که محتویات جدول [CONF] قابل بازیابی است؛
- ردههای ۱۱–۱۹: نمایش محتویات جدول [LISTES]. در اینجا هیچ آزمایشی وجود ندارد، فقط یک بررسی بصری؛
- خطوط ۲۱–۳۵: ما جدول [LISTES] را در پایگاه داده با تخصیص مقادیر به فیلدها از لیستهای جدول [voix, sieges, elimine] تغییر میدهیم؛
- خطوط ۳۷–۵۱: جدول [LISTES] مجدداً خوانده میشود و بررسی میشود که نتیجه با آنچه وارد شده مطابقت دارد؛
- خطوط ۵۲–۵۵: تأیید بصری؛
نتایج کنسول حاصل از پیادهسازی لایه [DAO] به شرح زیر است:
- خطوط ۱–۲۳: لاگهای Spring Test؛
- خطوط ۲۵–۲۶: محتویات جدول [CONF];
- خطوط ۲۷–۳۴: محتوای اولیه جدول [LISTES];
- خطوط ۳۵–۴۲: محتویات جدول [LISTES] پس از تخصیص مقادیر به فیلدهای موجود در [voix, sieges, elimine];
علاوه بر این، تست JUnit با موفقیت انجام شد:
![]() |
7.11. ایجاد آرشیو [with-dependencies] از لایه [dao]
پروژه نهایی معماری زیر را دارد:
![]() |
بیایید پیکربندی Maven پروژه را به یاد بیاوریم:
<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>istia.st.elections</groupId>
<artifactId>elections-dao-jdbc-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<!--MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- اسپرینگ -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- Tomcat JDBC -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
<!--کتابخانه jSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Logging -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<!-- پلاگینها -->
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>config.AppConfig</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!--برای نصب محصول پروژه در مخزن محلی Maven -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
![]() |
ما قصد داریم یک فایل JAR واحد تولید کنیم که شامل کلاسهای تمام JARهای فهرستشده در بالا و همچنین کلاسهای پروژه لایه [DAO] باشد. این کار با ایجاد تغییری در فایل [pom.xml] انجام میشود:
<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>istia.st.elections</groupId>
<artifactId>elections-jdbc-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.2.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
...
</dependencies>
<!-- پلاگینها -->
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>console.Main</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
</project>
- خطوط ۲۵–۳۷: یک افزونه Maven را برای تولید JAR پیکربندی میکند؛
- خط ۱۵: نسخهٔ جاوایی را که باید برای کامپایل استفاده شود، مشخص میکند؛
پس از اعمال این تغییر، میتوان JAR را به صورت زیر در [1-10] تولید کرد:
![]() |
![]() |
- در [5]، با استفاده از دکمه [6] پوشه پروژه را انتخاب کنید؛
- در [7]، به پیکربندی اجرا یک نام بدهید؛
- در [8]، فهرست وظایف Maven را که باید اجرا شوند وارد کنید:
- [clean]: پوشهٔ [target] پروژه را پاک میکند که JAR تولیدشده در آن قرار میگیرد؛
- [compile]: پروژه را کامپایل میکند؛
- [assembly:single]: یک فایل JAR واحد حاوی تمام کلاسهای پروژه و وابستگیهای آن ایجاد میکند؛
![]() |
- در [9-10]، بررسی کنید که شما یک JDK (مجموعه توسعه جاوا) دارید و نه یک JRE (محیط اجرایی جاوا). تفاوت این است که JDK شامل یک کامپایلر است، در حالی که JRE شامل آن نیست. اگر JDK را ندارید، باید یکی را با استفاده از [11] به [10] اضافه کنید. برای این کار، مراحل توصیفشده در بخش ۳.۱ را دنبال کنید؛
![]() |
- در [17]، پیکربندی اجرای را اجرا کنید؛
- در [13]، آرشیو تولید شده؛
میتوانید این آرشیو را با استفاده از یک برنامهٔ فشردهسازی (unzip) باز کنید:
![]() |
7.12. آزمایش آرشیو لایه [DAO]
بیایید یک پروژه استاندارد Eclipse (نه Maven) [1] ایجاد کنیم:
![]() |
بیایید بسته [dao.console] را از پروژه [elections-dao-jdbc-01] در پروژههای [elections-dao-jdbc-02] و [2] کپی و پیست کنیم. خطاهای متعددی ظاهر میشوند زیرا کلاس [Main] به کلاسهایی ارجاع میدهد که در [Classpath] وجود ندارند. ما این مورد را اصلاح خواهیم کرد.
ابتدا، یک پوشه به نام [lib] را در پروژه جدید ایجاد میکنیم:
![]() |
![]() |
در [9]، آرشیو ایجادشده در مرحلهٔ قبل را در پوشهٔ [lib] قرار میدهیم، سپس مسیر ساخت (Build Path) پروژه را اصلاح میکنیم:
![]() |
![]() |
![]() |
- به [18]؛ ما آرشیو لایه [DAO] که قبلاً ایجاد شده بود را وارد کردهایم؛
![]() |
- در [19]، پروژه دیگر هیچ خطایی ندارد؛
سپس میتوانیم کلاس [Main] را اجرا کنیم. همان نتایج قبلی را بهدست میآوریم.







































