如何在Spring Boot+ReactJS的MySQL员工表中存储头像(非BLOB优先)
员工头像存储解决方案(Spring Boot + MySQL)
一、优先方案:文件存储+数据库保存访问路径
这是行业通用的最佳实践,避免直接在数据库存储二进制文件导致的性能问题,同时便于文件的管理和CDN加速。
1. 数据库表调整
给employees表新增avatar_url字段,用于存储头像文件的访问路径/文件名:
ALTER TABLE employees ADD COLUMN avatar_url VARCHAR(500) COMMENT '员工头像访问路径';
2. 修改Employee.java实体类
添加对应字段及相关方法:
package net.employee_crud.springboot.model; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; @Entity @Table(name="employees") public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; @Column(name = "first_name") private String firstName; @Column(name = "last_name") private String lastName; @Column(name = "email_id") private String emailId; @Column(name = "department_name") private String department; // 新增头像路径字段 @Column(name = "avatar_url") private String avatarUrl; public Employee() { } // 更新带头像路径的构造方法 public Employee(String firstName, String lastName, String emailId, String department, String avatarUrl) { super(); this.firstName = firstName; this.lastName = lastName; this.emailId = emailId; this.department = department; this.avatarUrl = avatarUrl; } // 原有getter/setter保持不变,新增以下方法 public String getAvatarUrl() { return avatarUrl; } public void setAvatarUrl(String avatarUrl) { this.avatarUrl = avatarUrl; } // 其他原有方法... }
3. 修改EmployeeController.java,支持头像上传
新增单独的头像上传接口,同时可调整创建/更新接口支持头像路径传入:
package net.employee_crud.springboot.controller; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import net.employee_crud.springboot.exception.ResourceNotFounudException; import net.employee_crud.springboot.model.Employee; import net.employee_crud.springboot.repository.EmployeeRepository; @CrossOrigin(origins = "http://localhost:3000") @RestController @RequestMapping("/api/v1/") public class EmployeeController { @Autowired private EmployeeRepository employeeRepository; // 本地文件存储路径(可配置到application.properties) private static final String UPLOAD_DIR = "src/main/resources/static/avatars/"; // 原有CRUD接口保持不变... // 新增:上传员工头像接口 @PostMapping("/employees/{id}/avatar") public ResponseEntity<String> uploadAvatar(@PathVariable Long id, @RequestParam("file") MultipartFile file) { // 1. 校验员工是否存在 Employee employee = employeeRepository.findById(id) .orElseThrow(() -> new ResourceNotFounudException("Employee Not exist with id: " + id)); // 2. 校验文件类型和大小 if (file.isEmpty() || !file.getContentType().startsWith("image/")) { return ResponseEntity.badRequest().body("请上传有效的图片文件"); } // 3. 生成唯一文件名避免覆盖 String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename(); File uploadPath = new File(UPLOAD_DIR); // 4. 创建存储目录(如果不存在) if (!uploadPath.exists()) { uploadPath.mkdirs(); } try { // 5. 保存文件到本地 file.transferTo(new File(UPLOAD_DIR + fileName)); // 6. 更新数据库中的头像路径(这里用相对路径,前端可拼接访问地址) String avatarUrl = "/avatars/" + fileName; employee.setAvatarUrl(avatarUrl); employeeRepository.save(employee); return ResponseEntity.ok("头像上传成功,访问路径:" + avatarUrl); } catch (IOException e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("头像上传失败:" + e.getMessage()); } } // 可选:修改创建员工接口,支持传入头像路径 @PostMapping("/employees") public Employee createEmployee(@RequestBody Employee employee) { return employeeRepository.save(employee); } // 可选:修改更新员工接口,支持更新头像路径 @PutMapping("/employees/{id}") public ResponseEntity<Employee> updateEmployee(@PathVariable Long id,@RequestBody Employee employee_details){ Employee employee = employeeRepository.findById(id) .orElseThrow(()-> new ResourceNotFounudException("Employee Not exist with id: " + id)); employee.setFirstName(employee_details.getFirstName()); employee.setLastName(employee_details.getLastName()); employee.setEmailId(employee_details.getEmailId()); employee.setDepartment(employee_details.getDepartment()); // 新增更新头像路径 if (employee_details.getAvatarUrl() != null) { employee.setAvatarUrl(employee_details.getAvatarUrl()); } Employee updated_Employee = employeeRepository.save(employee); return ResponseEntity.ok(updated_Employee); } // 其他原有接口... }
4. 配置Spring Boot静态资源访问
如果使用本地存储,需要确保前端能访问到头像文件,可在application.properties中添加:
spring.web.resources.static-locations=classpath:/static/ spring.mvc.static-path-pattern=/**
二、备选方案:基于BLOB的数据库存储方案
仅当业务必须将头像存储在数据库时使用,此方案会增加数据库负担,不推荐大文件场景。
1. 数据库表调整
给employees表新增avatar字段,类型为LONGBLOB:
ALTER TABLE employees ADD COLUMN avatar LONGBLOB COMMENT '员工头像二进制数据';
2. 修改Employee.java实体类
添加二进制头像字段,使用@Lob注解标记:
package net.employee_crud.springboot.model; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Lob; import jakarta.persistence.Table; @Entity @Table(name="employees") public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; @Column(name = "first_name") private String firstName; @Column(name = "last_name") private String lastName; @Column(name = "email_id") private String emailId; @Column(name = "department_name") private String department; // 新增头像二进制字段 @Lob @Column(name = "avatar") private byte[] avatar; public Employee() { } // 更新构造方法(可选,根据业务是否需要) public Employee(String firstName, String lastName, String emailId, String department, byte[] avatar) { super(); this.firstName = firstName; this.lastName = lastName; this.emailId = emailId; this.department = department; this.avatar = avatar; } // 新增getter/setter public byte[] getAvatar() { return avatar; } public void setAvatar(byte[] avatar) { this.avatar = avatar; } // 其他原有方法... }
3. 修改EmployeeController.java,支持BLOB头像上传与获取
package net.employee_crud.springboot.controller; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import net.employee_crud.springboot.exception.ResourceNotFounudException; import net.employee_crud.springboot.model.Employee; import net.employee_crud.springboot.repository.EmployeeRepository; @CrossOrigin(origins = "http://localhost:3000") @RestController @RequestMapping("/api/v1/") public class EmployeeController { @Autowired private EmployeeRepository employeeRepository; // 原有CRUD接口保持不变... // 新增:上传BLOB头像接口 @PostMapping("/employees/{id}/avatar/blob") public ResponseEntity<String> uploadAvatarBlob(@PathVariable Long id, @RequestParam("file") MultipartFile file) { Employee employee = employeeRepository.findById(id) .orElseThrow(() -> new ResourceNotFounudException("Employee Not exist with id: " + id)); if (file.isEmpty() || !file.getContentType().startsWith("image/")) { return ResponseEntity.badRequest().body("请上传有效的图片文件"); } try { // 将文件转成字节数组存入数据库 byte[] avatarBytes = file.getBytes(); employee.setAvatar(avatarBytes); employeeRepository.save(employee); return ResponseEntity.ok("头像上传成功"); } catch (IOException e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("头像上传失败:" + e.getMessage()); } } // 新增:获取BLOB头像接口 @GetMapping("/employees/{id}/avatar/blob") public ResponseEntity<byte[]> getAvatarBlob(@PathVariable Long id) { Employee employee = employeeRepository.findById(id) .orElseThrow(() -> new ResourceNotFounudException("Employee Not exist with id: " + id)); if (employee.getAvatar() == null) { return ResponseEntity.noContent().build(); } // 设置响应头,指定图片类型 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.IMAGE_JPEG); // 可根据实际文件类型调整 return new ResponseEntity<>(employee.getAvatar(), headers, HttpStatus.OK); } // 其他原有接口... }
内容的提问来源于stack exchange,提问作者Rishabh Teli
相关产品推荐
相关产品推荐

