Khi làm việc với các dự án Java web, việc tích hợp nhiều công nghệ thường gặp khó khăn ở khâu cấu hình. Bài viết này hướng dẫn cách xây dựng hệ thống quản lý nhân viên sử dụng Spring MVC kết hợp MyBatis (thay vì Spring JDBC), Bootstrap cho giao diện và Ajax để xử lý bất đồng bộ.
Thiết kế cơ sở dữ liệu
Tạo database hr_management với hai bảng chính: phòng ban và nhân viên.
-- Tạo database
CREATE DATABASE IF NOT EXISTS hr_management
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE hr_management;
-- Bảng phòng ban
CREATE TABLE department (
dept_no INT AUTO_INCREMENT PRIMARY KEY,
dept_title VARCHAR(50) NOT NULL COMMENT 'Tên phòng ban'
) ENGINE=InnoDB;
-- Bảng nhân viên
CREATE TABLE staff (
staff_no INT AUTO_INCREMENT PRIMARY KEY,
fullname VARCHAR(100) NOT NULL COMMENT 'Họ tên',
sex CHAR(1) COMMENT 'Giới tính: M/F',
mail VARCHAR(100) COMMENT 'Email liên hệ',
dept_no INT COMMENT 'Mã phòng ban',
FOREIGN KEY (dept_no) REFERENCES department(dept_no)
) ENGINE=InnoDB;
Cấu trúc dự án Maven
File pom.xml với các dependency cần thiết:
<dependencies>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.20</version>
</dependency>
<!-- MyBatis + Spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!-- Connection Pool -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.11</version>
</dependency>
<!-- JSON processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
</dependencies>
Cấu hình Spring
db.properties - Thông tin kết nối:
db.url=jdbc:mysql://127.0.0.1:3306/hr_management?useSSL=false&serverTimezone=Asia/Ho_Chi_Minh&allowPublicKeyRetrieval=true
db.driver=com.mysql.cj.jdbc.Driver
db.account=root
db.secret=your_password
spring-context.xml - Cấu hình bean:
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${db.url}"/>
<property name="driverClassName" value="${db.driver}"/>
<property name="username" value="${db.account}"/>
<property name="password" value="${db.secret}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
<property name="typeAliasesPackage" value="com.demo.entity"/>
</bean>
<mybatis-spring:scan base-package="com.demo.dao"/>
Entity và Mapper
Class Staff.java:
public class Staff {
private Integer staffNo;
private String fullname;
private String sex;
private String mail;
private Integer deptNo;
private Department department; // Đối tượng phòng ban
// Getters, setters, constructors
}
Interface StaffDao.java:
@Repository
public interface StaffDao {
List<Staff> selectWithDept();
int insertSelective(Staff record);
int deleteByPrimaryKey(Integer staffNo);
int updateByPrimaryKeySelective(Staff record);
Staff selectByPrimaryKey(Integer staffNo);
}
File mapper StaffMapper.xml:
<mapper namespace="com.demo.dao.StaffDao">
<resultMap id="withDept" type="Staff">
<id column="staff_no" property="staffNo"/>
<result column="fullname" property="fullname"/>
<result column="sex" property="sex"/>
<result column="mail" property="mail"/>
<association property="department">
<id column="dept_no" property="deptNo"/>
<result column="dept_title" property="deptTitle"/>
</association>
</resultMap>
<select id="selectWithDept" resultMap="withDept">
SELECT s.*, d.dept_title
FROM staff s
LEFT JOIN department d ON s.dept_no = d.dept_no
</select>
</mapper>
Controller xử lý Ajax
@RestController
@RequestMapping("/api/staff")
public class StaffController {
@Autowired
private StaffDao staffDao;
@GetMapping("/list")
public ResponseEntity<List<Staff>> fetchAll() {
return ResponseEntity.ok(staffDao.selectWithDept());
}
@PostMapping("/add")
public ResponseEntity<String> create(@RequestBody Staff staff) {
int rows = staffDao.insertSelective(staff);
return rows > 0 ? ResponseEntity.ok("SUCCESS") :
ResponseEntity.status(500).body("FAILED");
}
@PutMapping("/update/{id}")
public ResponseEntity<String> modify(@PathVariable Integer id,
@RequestBody Staff staff) {
staff.setStaffNo(id);
return staffDao.updateByPrimaryKeySelective(staff) > 0 ?
ResponseEntity.ok("UPDATED") :
ResponseEntity.badRequest().body("ERROR");
}
@DeleteMapping("/remove/{id}")
public ResponseEntity<String> erase(@PathVariable Integer id) {
return staffDao.deleteByPrimaryKey(id) > 0 ?
ResponseEntity.ok("DELETED") :
ResponseEntity.notFound().build();
}
}
Giao diện Bootstrap + Ajax
Trang JSP với Bootstrap 5:
<!DOCTYPE html>
<html>
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-4">
<h3>Quản lý nhân sự</h3>
<button class="btn btn-primary mb-3" onclick="openModal()">Thêm mới</button>
<table class="table table-bordered">
<thead>
<tr>
<th>Mã NV</th>
<th>Họ tên</th>
<th>Giới tính</th>
<th>Phòng ban</th>
<th>Thao tác</th>
</tr>
</thead>
<tbody id="dataBody"></tbody>
</table>
</div>
<script>
// Load dữ liệu khi trang ready
document.addEventListener('DOMContentLoaded', loadData);
function loadData() {
fetch('/api/staff/list')
.then(r => r.json())
.then(data => {
const tbody = document.getElementById('dataBody');
tbody.innerHTML = data.map(item => `
<tr>
<td>${item.staffNo}</td>
<td>${item.fullname}</td>
<td>${item.sex === 'M' ? 'Nam' : 'Nữ'}</td>
<td>${item.department?.deptTitle || '-'}</td>
<td>
<button class="btn btn-sm btn-warning" onclick="editRow(${item.staffNo})">Sửa</button>
<button class="btn btn-sm btn-danger" onclick="removeRow(${item.staffNo})">Xóa</button>
</td>
</tr>
`).join('');
});
}
function removeRow(id) {
if (!confirm('Xác nhận xóa?')) return;
fetch(`/api/staff/remove/${id}`, { method: 'DELETE' })
.then(r => r.ok ? loadData() : alert('Xóa thất bại'));
}
function saveData(payload) {
const method = payload.staffNo ? 'PUT' : 'POST';
const url = payload.staffNo ? `/api/staff/update/${payload.staffNo}` : '/api/staff/add';
fetch(url, {
method: method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
}).then(r => r.ok && loadData());
}
</script>
</body>
</html>
Lưu ý khi triển khai
- Phiên bản MySQL 8.x yêu cầu driver
com.mysql.cj.jdbc.Drivervà tham sốserverTimezone - Thêm
allowPublicKeyRetrieval=truenếu gặp lỗi authentication với MySQL 8 - Đảm bảo dispatcher servlet mapping trong
web.xmlhoặc classWebAppInitializer - Thư mục
mappercần được build vào classpath (cấu hình trong pom.xml)