7. [TD]:使用API和JDBC实现TD的[DAO]层
关键词:关系型数据库,API JDBC,SQLException。
让我们回顾一下应用程序的分层架构:
![]() |
选举所需的数据存储在数据库中 MySQL [dbelections]
7.1. Support
![]() |
文件夹 [support / chap-07] [1] 包含:
- 本章的 Eclipse 项目 [2];
- 用于创建数据库的脚本 SQL MySQL [dbelections] [3];
7.2. 数据库 [dbelections]
待完成任务:按照第 6.4.2 节中的步骤,创建数据库 MySQL 和 [dblelections]。
数据库 [dbelections] 是数据库 MySQL,其中包含两个表:
![]() |
表 [conf] 包含选举配置信息:
![]() |
- [id]:自增主键;
- [version]:记录版本号——此处可忽略;
- [sap]:待填补席位数;
- [seuilelectoral]:低于该阈值将被剔除的列表;
其内容如下:
![]() |
表 [listes] 包含本次选举的候选名单:
![]() |
- [id]:自增主键;
- [version]:记录版本号——此处可忽略;
- [nom]:名单名称;
- [voix]:列表的选票数——仅在用户在[présentation]层中输入后才知晓;
- [sieges]:获得的席位数——仅在计算完图层 [métier] 后才知晓;
- [elimine]:若名单被淘汰则为1,否则为0——仅在计算完[métier]层后才确定;
表 [listes] 的内容如下:
![]() |
用于生成数据库 [dbelections] 的脚本 SQL 名为 [dbelections.sql],位于服务器上。其代码如下:
-- phpMyAdmin SQL 转储
-- 版本 4.0.4
-- http://www.phpmyadmin.net
--
-- 客户端:localhost
-- 生成时间:2015年3月11日 星期三 12:20
-- 服务器版本: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. Eclipse 项目
![]() |
[DAO] 层的 Eclipse 项目将如下所示:
![]() |
- 包 [elections.dao.entities] 包含由层 [DAO] 操作的对象;
- 包 [elections.dao.service] 包含层 [DAO] 的实现;
- 包 [elections.dao.config] 包含 [DAO] 层的配置
- 包 [elections.dao.junit] 包含该项目中的测试类 JUnit;
- 包 [elections.dao.console] 包含一个可执行的测试类;
7.4. Maven 项目配置
![]() |
用于配置 Maven 项目的文件 [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-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>
<!-- Spring -->
<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 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot 日志 -->
<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 节中描述的文件类似。其中进行了以下修改:
- 第 8-12 行:定义了一个父级 Maven 项目。 项目 [spring-boot-starter-parent](第 10 行)定义了大量依赖项及其版本。当使用其中一项(第 19-57 行)时,无需指定其版本,因为该版本已在父 Maven 项目中定义;
- 第40-51行:项目测试类所需的依赖项。这些依赖项带有[<scope>test</scope>]属性,表示它们仅对[src / test / java]文件夹中的类有效。这些依赖项不会包含在项目的最终归档中;
- 第 53-56 行:Spring 将使用 [spring-boot-starter-logging] 库在屏幕上生成日志;
- 第 14-17 行:Maven 配置属性:
- 第 15 行:指定源文件使用 UTF-8 语言编写;
- 第 16 行:指定应使用 1.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;
// Elections 应用程序的异常类
// 该异常为非受控异常
public class ElectionsException extends RuntimeException implements Serializable {
// 序列 ID
private static final long serialVersionUID = 1L;
// 本地字段
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) {
// 父级
super();
// 本地
this.code = code;
this.erreurs = erreurs;
}
// 异常的错误消息列表
private List<String> getErreursForException(Throwable th) {
// 获取异常的错误消息列表
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// 仅当错误消息不为空且不为空时才获取该消息
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// 原因如下
cause = cause.getCause();
}
return erreurs;
}
// getter 和 setter
...
}
一个 [ElectionsException] 对象由两项信息构成:
- 第 16 行:一个错误代码;
- 第17行:与已发生的异常堆栈相关的错误消息列表;
- 该类有 5 个构造函数(第 20、24、32、40、50 行);
- 第 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;
// 字段
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;
}
}
// getter 和 setter
...
}
它存储了表 [CONF] 和 [LISTES] 中各行的 [ID, NOM] 字段(第 8-9 行)。
- 第8行:该类具有属性[Abstract],表明它不能被实例化。它只能被派生;
- 第26-33行:对象的签名jSON;
- 第28行:返回来自[this]的字符串jSON。 若在运行时,[this] 表示一个从 [AbstactEntity] 派生的对象,则将获得该派生对象的字符串 jSON。 因此,派生类无需定义 [toString] 方法。父类的该方法已足够;
7.5.3. 类 [ElectionsConfig]
![]() |
[ElectionsConfig] 类如下:
package elections.dao.entities;
public class ElectionsConfig extends AbstractEntity {
private static final long serialVersionUID = 1L;
// 字段
private int nbSiegesAPourvoir;
private double seuilElectoral;
// 构造函数
public ElectionsConfig() {
}
public ElectionsConfig(Long id, Long version, int nbSiegesAPourvoir, double seuilElectoral) {
// 父类
super(id, version);
// 局部字段
this.nbSiegesAPourvoir = nbSiegesAPourvoir;
this.seuilElectoral = seuilElectoral;
}
// getter 和 setter
...
}
- 第 4 行:该类继承自类 [AbstractEntity];
- 第8-9行:存储表[CONF]中[SAP, SEUILELECTORAL]列的信息;
7.5.4. 类 [ListeElectorale]
![]() |
类 [ListeElectorale] 如下:
package elections.dao.entities;
public class ListeElectorale extends AbstractEntity {
// 字段
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();
// 本地字段
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);
// 本地字段
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;
}
}
- 第 3 行:该类继承自类 [AbstractEntity];
- 第6-9行:该类存储表[LISTES]中的列[NOM, VOIX, SIEGES, ELIMINE];
7.6. [DAO]层的Spring配置
![]() |
![]() |
类 [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");
}
}
- 第 25-38 行:将通过数据源 [tomcat-jdbc] 访问 BD。第 6.5 节中已使用并解释了此类数据源;
- 第17-23行:一组静态常量,项目中的所有类均可访问;
- 第 13 行:注解 [@Configuration] 将类 [AppConfig] 设为 Spring 配置类;
- 第 11 行:注解 [@ComponentScan] 指明了可找到 Spring 对象的包。在此,我们将定义一个位于包 [dao] 中的 Spring 对象。 注解 [@ComponentScan] 将该类设为配置类,从而省去了添加注解 [@Configuration] 的步骤;
- 第 12 行启用了缓存管理。其原理如下:
- 在方法 M 上添加注解 [@Cacheable('nom_du_cache')]。'缓存名称' 即第 41 行中使用的名称;
- 当方法 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>
关键在于第12行,该处设定了所需的信息级别:
- [debug]:最详细的级别;
- [off]:不生成日志;
- [info]:常规日志级别;
- [warn]:与 [info] 级别相同,但额外包含警告消息。这些消息提示可能存在错误;
一旦运行时出现错误,请立即切换至 [debug] 模式。
7.8. [DAO] 层的实现
![]() |
![]() |
[DAO] 层的 [IDao] 接口如下:
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);
}
实现 [dao] 层并连接数据库的 [ElectionsDaoJdbc] 类的骨架如下:
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;
}
}
- 第 24 行:注解 [@Cacheable] 是一个 Spring 注解,用于将方法 [getElectionsConfig] 的结果缓存到内存中。之所以可以在此处这样做,是因为表 [CONF] 的内容永远不会改变。 无法将此注解应用于方法 [getListesElectorales],因为表 [LISTES] 的内容会随时间变化;
- 方法 [doCatchException] 和 [doFinally] 返回类型为 [ElectionsException]。 如果资源释放成功,方法 [doFinally] 将返回一个 null 指针;
待完成任务:参考第 6.5.2 节中学习的 [IntroJdbc02] 类,编写 [ElectionDaoJdbc] 类。
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) {
// 在实例化 Spring 上下文后获取 [DAO] 层的引用
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) {
// 仅当消息不为空且不为空时才获取消息
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// 下一个原因
cause = cause.getCause();
}
return erreurs;
}
}
- 第21-31行:获取[DAO]层的引用;
- 第21行:使用名为try_with_resources的语法。其语法如下:
try (T ressource=expression) {
// 资源利用
...
}
- (续)
- 第 1 行的类型 T 必须实现接口 [java.lang.AutoCloseable];
- 在第1-4行代码块结束时,无论是否发生异常,类型为[java.lang.AutoCloseable]的资源都会被自动释放。熟悉C#语言的人会发现,这在语法和功能上与using子句(T resource=expression)如出一辙;
- 第40-49行:使用[DAO]层获取[CONF]和[LISTES]表的内容;
- 第 41-47 行:测试缓存 [electionsconfig]。该缓存已在两个位置进行定义:
- 在类 [ElectionsDaoJdbc] 中:
@Cacheable("electionsConfig")
public ElectionsConfig getElectionsConfig() {
- (续)
- 在配置类 [AppConfig] 中:
@EnableCaching
public class AppConfig {
...
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- 第59行:关闭Spring上下文;
- 第62-67行:显示获取的信息;
实现 [DAO] 层后,结果如下:
- 第2-4行:显示缓存的影响:
- 请求 1 耗时 80 毫秒;
- 查询 2 耗时 1 毫秒;
当数据库断开时,结果如下:
7.10. [ElectionsDaoJdbc] 类的测试 JUnit
![]() |
![]() |
类 [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();
// 将票数和席位清零,并将状态设为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() {
...
}
}
- 第 17 行:注解 [RunWith] 作为注解 [JUnit](第 6 行)的子类,通过类 [SpringJUnit4ClassRunner] 实现与 Spring 的集成;
- 第 16 行:注解 [SpringApplicationConfiguration] 是注解 [Spring](第 8 行)的实例,用于指定测试配置类 JUnit。 此处指定了用于配置项目的类 [AppConfig]。这样,我们便拥有了该配置类定义的所有 Spring 对象。因此,我们可以向第 21-22 行注入对待测试的 [DAO] 层的引用;
- 第25行:注解[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]);
}
}
- 第 5-9 行:确保能够检索表 [CONF] 的内容;
- 第11-19行:显示表[LISTES]的内容。此处不进行测试,仅进行目视检查;
- 第21-35行:在数据库中修改表[LISTES],为列表的[voix, sieges, elimine]字段赋予值;
- 第37-51行:重新读取表[LISTES],并验证所得结果是否与写入内容一致;
- 第 52-55 行:进行目视检查;
已实现 [DAO] 层后,控制台显示的结果如下:
- 第 1-23 行:Spring Test 日志;
- 第25-26行:表[CONF]的内容;
- 第 27-34 行:表 [LISTES] 的初始内容;
- 第 35-42 行:为字段 [voix, sieges, elimine] 赋值后,表 [LISTES] 的内容;
此外,测试 JUnit 成功:
![]() |
7.11. 创建来自层 [dao] 的归档 [with-dependencies]
最终项目的架构如下:
![]() |
回顾一下该项目的 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>
<!-- Spring -->
<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 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot 日志 -->
<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>
- 第 25-37 行:配置一个 Maven 插件以生成 JAR 文件;
- 第 15 行:指定编译时使用的 Java 版本;
完成此修改后,可通过以下方式生成 JAR 文件 [1-10]:
![]() |
![]() |
- 在 [5] 中,使用 [6] 按钮选择项目文件夹;
- 在 [7] 中,为运行配置命名;
- 在 [8] 中,填写要执行的 Maven 目标列表:
- [clean]:清空项目中的 [target] 文件夹,生成的 jar 文件将放置于此;
- [compile]:编译项目;
- [assembly:single]:生成一个包含项目及其所有依赖类文件的单一 JAR 文件;
![]() |
- 在 [9-10] 中,请确认您使用的是 JDK(Java 开发工具包),而非 JRE(Java 运行时环境)。 两者的区别在于:JDK包含编译器,而JRE不包含。 如果您没有 JDK,则需要在 [10] 中添加 [11]。为此,请按照第 3.1 节所述的步骤操作;
![]() |
- 在 [17] 中,执行运行配置;
- 在 [13] 中,生成的归档文件;
可使用解压工具打开该压缩包:
![]() |
7.12. 测试 [DAO] 层压缩包
创建一个标准 Eclipse 项目(非 Maven)[1]:
![]() |
将项目 [elections-dao-jdbc-01] 中的包 [dao.console] 复制并粘贴到项目 [elections-dao-jdbc-02] [2] 中。 由于类 [Main] 引用了其 [Classpath] 中不存在的类,因此出现了许多错误。我们将修改该类。
首先,我们在新项目中创建文件夹 [lib] 并创建 [2-8]:
![]() |
![]() |
在 [9] 中,我们将上一步创建的压缩包放入 [lib] 文件夹,然后修改项目的构建路径:
![]() |
![]() |
![]() |
- 修改为 [18],已导入之前创建的 [DAO] 图层归档文件;
![]() |
- 在 [19] 中,项目不再出现错误;
此时可以执行类 [Main]。结果与之前相同。







































