讲讲在Spring Boot项目中如何建立数据库和管理
随着智能门禁、考勤、访客系统的普及,人脸识别模块 已成为众多项目的重要组成部分。在高性能的人脸识别系统中,数据库主要负责持久化存储 ,而识别比对操作应全部在内存中完成 ,以保证毫秒级响应和高并发性能。本文将介绍如何在 Spring Boot 项目中建立一个高效、可扩展的人脸识别数据库,并实现基础的人脸数据管理功能,同时结合 MySQL、Redis 与内存优化策略,提升系统整体性能。
一、基础入门介绍
这里为了能让大家对人脸识别SDK结合数据库有个初步了解,我们先利用 ArcSoftFaceDemo ,讲解下此Demo是如何对人脸数据库进行管理的,以及如何正确地注册到 ArcFace SDK 的引擎中,以支持后续的人脸检测和识别功能。下面结合代码分析整个管理流程。
1、数据库环境准备
在开始使用 ArcsoftFaceDemo 前,需要先准备好 MySQL 数据库环境。为了快速搭建,可以直接使用 Docker 启动一个数据库实例。
使用 Docker 启动 MySQL 8.0
执行以下命令启动 MySQL 容器:
bash
docker run -d \
--name arcsoft_mysql \
-e MYSQL_ROOT_PASSWORD=Root123 \
-e MYSQL_DATABASE=arcsoft_face \
-p 3306:3306 \
registry.cn-hangzhou.aliyuncs.com/china-images/mysql:8.0
说明:
--name arcsoft_mysql:容器名称,方便管理。MYSQL_ROOT_PASSWORD=Root123:root 用户密码。MYSQL_DATABASE=arcsoft_face:初始化创建数据库arcsoft_face。-p 3306:3306:将容器端口映射到宿主机,方便程序连接。registry.cn-hangzhou.aliyuncs.com/china-images/mysql:8.0:指定使用 MySQL 8.0 阿里云镜像,如果有外网环境,可直接使用mysql:8.0代替。
如果你想了解更多 Docker 使用方式,可以参考其他 Docker 入门文档或教程。也可直接在物理操作系统上安装MySQL
数据库连接信息
启动完成后,你就可以通过以下信息连接数据库:
- URL :
物理机IP:3306 - 用户名 :
root - 密码 :
Root123 - 数据库 :
arcsoft_face
这样就完成了数据库环境的搭建,为 ArcsoftFaceDemo 的人脸库管理做好了基础准备。
2、工程导入与数据库配置
在完成数据库环境准备后,下一步是导入 ArcSoftFaceDemo 工程,并将默认数据库配置改为 MySQL。
导入工程
- 从 ArcSoft 官网下载 ArcSoftFaceDemo 工程压缩包。
- 解压后,用 IntelliJ IDEA 打开工程目录。
修改数据库配置
工程默认使用的是 H2 内存数据库 ,适合快速演示和测试。但在正式环境中,建议切换到 MySQL 或其他持久化数据库。
打开 application.properties 文件,找到默认配置:
properties
spring.datasource.url=jdbc:h2:file:./data/arcdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.sql.init.mode=always
spring.sql.init.schema-locations=classpath:db/schema-h2.sql
说明:
spring.datasource.url:H2 数据库文件路径。spring.datasource.driver-class-name:H2 数据库驱动。spring.datasource.username/spring.datasource.password:数据库账号密码。spring.h2.console.*:H2 Web 控制台相关配置,方便调试。spring.sql.init.*:初始化 SQL 文件路径,用于创建示例表结构。
H2切换为MySQL
在正式环境中,我们需要将默认 H2 配置改为 MySQL。打开 application.properties,将数据库相关配置修改为 MySQL 连接信息:
properties
spring.datasource.url=jdbc:mysql://localhost:3306/arcsoft_face?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=Root123
spring.sql.init.mode=always
spring.sql.init.schema-locations=classpath:db/schema-mysql.sql
说明:
spring.datasource.url:MySQL 连接 URL,包含数据库名arcsoft_face,字符集与时区设置。localhost地址根据实际数据库IP地址设置spring.datasource.driver-class-name:MySQL 驱动类。spring.datasource.username/spring.datasource.password:MySQL 账号密码,对应 Docker 启动 MySQL 时设置的用户名和密码。spring.sql.init.*:初始化 SQL 文件路径,用于创建用户表结构(与 H2 不同,需要使用 MySQL 版本 SQL)。
验证数据库连接
-
启动 ArcSoftFaceDemo 工程。
-
检查启动日志,确认数据库连接成功:
HikariPool-1 - Starting...
HikariPool-1 - Start completed.
注意:使用 MySQL 客户端(如 mysql 命令行或 Navicat等)连接数据库,确认 arcsoft_face 数据库已创建成功。
创建用户表
如果 MySQL 数据库中没有表,需要执行初始化 SQL 创建 user_info 表:
sql
CREATE TABLE IF NOT EXISTS user_info (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(60) NOT NULL,
extra_info VARCHAR(500),
face_feature BLOB,
image_url VARCHAR(120),
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY user_info_name_uindex (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关键字段说明:
id:用户唯一标识。name:用户名。face_feature:ArcSoft SDK 提取的人脸特征二进制数据。这里也可以根据业务来,使用text类型,存储base64编码后的字符串数据image_url:注册证存储地址。
这样就完成了 MySQL 数据库接入 ArcSoftFaceDemo 的准备工作。
3、人脸注册过程说明(FaceService.java)
FaceService 是整个系统的人脸注册与管理核心逻辑,负责:
- 从数据库读取用户人脸信息
- 注册/更新人脸到 ArcFace SDK 内存引擎
- 删除人脸
- 系统启动时批量加载人脸
- 支持从数据库、本地图片、classpath 目录三种来源初始化
下面详细说明各功能模块的逻辑。
人脸注册核心方法:addUser
java
public void addUser(String name, byte[] faceFeature) {
UserInfo userInfo = userInfoMapper.selectOne(Wrappers.<UserInfo>lambdaQuery().eq(UserInfo::getName, name));
if (userInfo == null) {
UserInfo userInfoInsert = new UserInfo();
userInfoInsert.setId((int) (System.currentTimeMillis() % 1_000_000_000)); //如果是MySQL数据库,可去掉这个,利用数据库自增长id来生成
userInfoInsert.setName(name);
userInfoInsert.setFaceFeature(faceFeature);
userInfoMapper.insert(userInfoInsert);
faceEngineService.registerFaceFeature2Engine(userInfoInsert.getId(), name, faceFeature);
} else {
UserInfo userInfoUpdate = new UserInfo();
userInfoUpdate.setId(userInfo.getId());
userInfoUpdate.setName(name);
userInfoUpdate.setFaceFeature(faceFeature);
userInfoMapper.updateById(userInfoUpdate);
faceEngineService.removeFaceFeature2Engine(userInfo.getId());
faceEngineService.registerFaceFeature2Engine(userInfo.getId(), name, faceFeature);
}
}
功能说明
addUser 是 ArcSoftFaceDemo 项目中最核心的人脸注册方法,主要功能是:
- 将输入的人脸特征(byte[])写入数据库
- 将人脸特征注册到 ArcFace 引擎的内存特征库(内存中的索引结构)
- 同步维护两端的数据一致性(数据库 ↔ 识别引擎)
ArcFace SDK 的识别能力依赖于内存中的特征库,因此每次注册、更新、删除都必须保持数据库与 SDK 内存库一致。在做新增的时候,同步两边需同时新增,保证数据一致性。
这里注册存储的时候,也可以同时带一些业务上的信息,如人员id,人员姓名,人员注册照存放地址等。这些信息,在数据库中可以以字段的形式进行存储归档 ,可参考上面user_info表进行字段扩展优化。如果想通过SDK的searchFaceFeature接口获取到这些额外信息,在registerFaceFeature接口中提供了faceTag的一个字符串类型,用于存放业务数据,可以把这些信息以JSON字符串形式存放到faceTag中去,当然也可通过searchId,再查询数据库的方式,把对应的人员信息查询出来
ArcFace SDK 提取的人脸特征是一个 byte[] 数组。5.0 的中模型特征长度在 2056字节,大模型特征特征长度3080字节,这里在做数据库字段大小的时候需要关注下
两种存储方式:SDK的特征类型是byte[]数组类型,存储到数据库方式:
1、可以直接使用BLOB类型,一般推荐用BLOB类型存储,可以减少编解码带来的性能损失
2、byte[]数组转成base64字符串,数据库用TEXT类型进行存储,如果涉及到网络传输,可使用Base64字符串
删除用户 removeUser
java
public void removeUser(String name) {
UserInfo userInfo = userInfoMapper.selectOne(Wrappers.<UserInfo>lambdaQuery().eq(UserInfo::getName, name));
if (userInfo != null) {
faceEngineService.removeFaceFeature2Engine(userInfo.getId());
userInfoMapper.deleteById(userInfo.getId());
}
}
- 根据用户名查询用户
- 从 SDK 内存引擎移除人脸
- 从数据库删除记录
系统启动时自动加载人脸
java
@PostConstruct
public void loadImage2Engine() {
if ("true".equals(faceImageClear)) {
removeAllUser();
initSystemFace();
File file = new File(testImagePath);
if (file.isDirectory()) {
File[] files = file.listFiles();
log.info("开始加载本地人脸图片到引擎中");
for (File imageFile : files) {
String name = imageFile.getName();
if (name.endsWith(".jpg") || name.endsWith(".png")) {
String userName = name.substring(0, name.lastIndexOf("."));
register(userName, imageFile, null);
}
}
log.info("加载本地人脸图片到引擎中完成");
}
}else {
initDatabaseFace2Engine();
}
}
系统启动后执行,用于初始化内存人脸库,为了加快识别速度,建议每次启动服务后,就直接把数据库里的人脸注册信息放到SDK引擎里,减少下次识别读取数据库带来的耗时增加。Demo中提供了有两种模式,根据配置文件的register.faceimage.clean进行切换
register.faceimage.clean=true,每次启动会清空数据库,重新加载本地图片进行注册,同时存储到数据库和SDK
register.faceimage.clean=false,每次启动从数据库读取特征,加载到SDK。Demo为了测试,会同时把本地本次未注册的图片进行存储和注册,生产环境可只保留从数据库读取加载
4、人脸识别方式
这里再另外提一下,SDK人脸识别主要有以下两种方案,Demo采用的是第2种方案,也可根据实际业务使用第1种方案
①、使用 Map 列表 + compareFaceFeature 接口
- 原理 :将注册的人脸特征存放在 Map 或列表中,通过 SDK 的
compareFaceFeature接口进行循环比对。 - 优点:可灵活分组管理人脸库;支持多线程并发比对。
- 缺点:当人脸库量大时,频繁调用比对接口会增加性能损耗。
②、使用 SDK 提供的原生接口注册
- 原理 :利用
registerFaceFeature、removeFaceFeature、updateFaceFeature、searchFaceFeature组合接口,实现对人脸的增删改查 - 优点:SDK 原生实现比对,性能快,资源消耗少。
- 缺点:不支持分组比对;系统重启后需重新注册特征;多引擎并发时,每个引擎的人脸库需单独管理,维护复杂。
为了实现长久保存和分布式共享,人脸特征需进行合理存储
5、人脸引擎说明(FaceEngineService.java)
FaceEngineService 是人脸SDK 的核心部分,负责:
- 人脸检测、特征提取、活体检测、属性检测等
- 人员信息的新增、删除、修改、人脸搜索
下面详细说明各功能模块的逻辑。
人员新增:registerFaceFeature2Engine
java
public void registerFaceFeature2Engine(int searchId, String faceTag, byte[] faceFeature) {
FaceEngineFactory factory = (FaceEngineFactory) faceEngineComparePool.getFactory();
List<FaceEngine> allObjects = factory.getAllObjects();
FaceFeatureInfo faceFeatureInfo = new FaceFeatureInfo();
faceFeatureInfo.setSearchId(searchId);
faceFeatureInfo.setFaceTag(faceTag);
faceFeatureInfo.setFeatureData(faceFeature);
// log.info("Register:"+ JSON.toJSONString(faceFeatureInfo));
for (FaceEngine allObject : allObjects) {
int res = allObject.registerFaceFeature(faceFeatureInfo);
}、、
}
这里重点说明下,因为我们的FaceEngine用了线程池,同时初始化了多个引擎,所以我们在向SDK注册人员信息的时候,需要给每个引擎进行注册,删除同理。
人员新增:removeFaceFeature2Engine
java
public void removeFaceFeature2Engine(int searchId) {
FaceEngineFactory factory = (FaceEngineFactory) faceEngineComparePool.getFactory();
List<FaceEngine> allObjects = factory.getAllObjects();
for (FaceEngine allObject : allObjects) {
allObject.removeFaceFeature(searchId);
}
}
删除同注册接口,需要同步向所有FaceEngine库进行移除操作
二、人脸特征三层存储与分布式管理
上面已经介绍了基本的数据库接入和使用,那么在实际人脸识别场景下,系统的数据存储与识别通常采用三层设计:
- MySQL:负责持久化存储人脸特征信息,实现可靠的长期保存。
- Redis:用于异步更新与分布式缓存,实现多机器环境下的人脸特征共享。
- 内存(ConcurrentHashMap / 特征池):存储当前在线的活跃人脸特征,实现毫秒级识别。
这种设计保证了系统既能持久化存储,又能在识别时获得最佳性能。
在 Spring Boot 项目中,实现人脸特征的管理可以从数据库建表、服务层加载、Redis缓存、特征注册与比对几个方面进行详细设计。
在多台识别服务器部署的分布式环境中,人脸特征需要在各节点之间保持一致性、高可用和高性能。结合内存、Redis 和数据库三层存储,设计参考方案:
1、 启动加载策略
- 流程 :
- 每台识别服务器启动时,先从 Redis 拉取最新的人脸特征到本地内存缓存。
- 如果 Redis 中没有特征数据(如初次部署),则从 MySQL 数据库加载。Redis 可作为 内存缓存 + 异步更新机制,保证各节点识别数据一致性
- 加载后,内存缓存即可支持毫秒级人脸识别。
- 实现示例:
java
@PostConstruct
public void loadFeaturesOnStartup() {
Set<String> keys = redisTemplate.keys("face:*");
if (keys != null && !keys.isEmpty()) {
for (String key : keys) {
byte[] featureData =(byte[]) redisTemplate.opsForValue().get(key);
if (featureData != null) {
featureMap.put(key.replace("face:", ""), featureData);
}
}
} else {
// Redis为空,从数据库加载
List<FaceFeature> features = repository.findAll();
for (FaceFeature f : features) {
featureMap.put(f.getUserId(), f.getFeature());
redisTemplate.opsForValue().set("face:" + f.getUserId(), f.getFeature());
}
}
}
- 优势 :
- 避免每次识别都访问数据库,提高性能。
- 启动即加载最新特征,保证识别数据实时性。
2、 特征更新与同步策略
在分布式场景下,用户新增、删除或更新人脸特征时,需要保证所有节点内存保持一致:
-
更新顺序:
写内存:立即更新本地内存缓存,保证识别服务实时可用。
- 写 Redis:同步到 Redis,作为分布式共享层。
- 写数据库:异步持久化到 MySQL,保证长期数据安全。
-
同步机制:
- 利用 Redis Pub/Sub 或消息队列(Kafka、RabbitMQ)广播更新事件。
- 其他节点接收到更新事件后,刷新本地内存特征缓存。
-
示例逻辑:
public void updateFeature(String userId, byte[] feature) {
// 更新本地内存
featureMap.put(userId, feature);
// 更新 Redis
redisTemplate.opsForValue().set("face:" + userId, feature);
// 发布更新事件通知其他节点
redisTemplate.convertAndSend("face:update", userId);
// 异步更新数据库
asyncUpdateDatabase(userId, feature);
}// 订阅事件刷新内存
@EventListener
public void handleRedisUpdateEvent(Message message) {
String userId = (String) message.getBody();
String feature = redisTemplate.opsForValue().get("face:" + userId);
if (feature != null) {
featureMap.put(userId, feature);
}
}
通过合理的存储架构和内存优化,Spring Boot 项目的人脸识别系统可以在保证高性能的同时,实现可扩展、分布式的人脸管理功能,为智能门禁、考勤和访客系统提供稳定支撑。