项目需求分析与设计
定义系统功能和架构设计
需求分析步骤
确定业务需求
与客户沟通,明确系统需要解决的问题和核心功能点
常见错误
在没有明确需求的情况下直接开始编码,导致后期频繁修改
创建用例图
使用UML描述系统功能和用户交互
// 示例:用户管理模块用例
Actor: 管理员
Use Cases:
- 添加用户
- 删除用户
- 修改用户信息
- 查询用户列表
Actor: 普通用户
Use Cases:
- 查看个人信息
- 修改密码
- 退出登录
三层架构设计
表现层 (UI)
控制台/Swing界面
用户交互处理
业务逻辑层 (Service)
业务规则实现
事务管理
数据访问层 (DAO)
数据库操作
持久化逻辑
数据库设计与实现
设计数据库模型并实现SQL脚本
数据库设计步骤
实体关系建模
识别系统中的实体、属性及关系
// 用户实体
User {
id: INT (PK)
username: VARCHAR(50)
password: VARCHAR(100)
email: VARCHAR(100)
created_at: TIMESTAMP
}
// 订单实体
Order {
id: INT (PK)
user_id: INT (FK to User.id)
total_price: DECIMAL(10,2)
status: ENUM('CREATED','PAID','SHIPPED')
}
SQL脚本实现
创建DDL脚本定义数据库表结构
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
total_price DECIMAL(10,2) NOT NULL,
status ENUM('CREATED','PAID','SHIPPED') DEFAULT 'CREATED',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
数据库设计原则
1. 遵循数据库三大范式
2. 主键使用自增整数或UUID
3. 为常用查询字段创建索引
4. 使用外键维护数据一致性
5. 避免过度设计,保持结构简单
数据访问层开发(DAO)
实现数据库操作接口
DAO模式实现
定义DAO接口
使用接口声明数据库操作方法
public interface UserDao {
// 添加用户
void addUser(User user);
// 根据ID查找用户
User getUserById(int id);
// 更新用户信息
void updateUser(User user);
// 删除用户
void deleteUser(int id);
// 获取所有用户
List<User> getAllUsers();
}
JDBC实现DAO
使用JDBC实现数据访问逻辑
public class UserDaoJdbcImpl implements UserDao {
private final Connection connection;
public UserDaoJdbcImpl(Connection connection) {
this.connection = connection;
}
@Override
public User getUserById(int id) {
String sql = "SELECT * FROM users WHERE id = ?";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setInt(1, id);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
return new User(
rs.getInt("id"),
rs.getString("username"),
rs.getString("email")
);
}
} catch (SQLException e) {
throw new RuntimeException("数据库查询失败", e);
}
return null;
}
// 其他方法实现...
}
DAO层最佳实践
1. 使用PreparedStatement防止SQL注入
2. 确保关闭所有数据库资源(Connection, Statement, ResultSet)
3. 使用连接池管理数据库连接
4. 将SQL异常转换为业务异常
5. 考虑使用Spring JDBCTemplate简化JDBC代码
业务逻辑层开发(Service)
实现核心业务逻辑
Service层实现
定义服务接口
抽象业务方法供控制器调用
public interface UserService {
// 用户注册
boolean register(User user, String password);
// 用户登录
User login(String username, String password);
// 更新用户信息
void updateProfile(User user);
// 密码重置
void resetPassword(int userId, String newPassword);
}
实现服务逻辑
协调多个DAO操作,实现业务规则
public class UserServiceImpl implements UserService {
private final UserDao userDao;
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public boolean register(User user, String password) {
// 检查用户名是否已存在
if (userDao.findByUsername(user.getUsername()) != null) {
throw new UserException("用户名已被使用");
}
// 密码加密
String hashedPassword = hashPassword(password);
user.setPassword(hashedPassword);
// 保存用户
return userDao.save(user) > 0;
}
private String hashPassword(String password) {
// 使用BCrypt加密
return BCrypt.hashpw(password, BCrypt.gensalt());
}
// 其他方法实现...
}
常见错误:事务问题
服务方法中多个DAO操作应该放在同一个事务中:
// 错误:缺少事务管理
public void transferMoney(int fromId, int toId, BigDecimal amount) {
accountDao.deductBalance(fromId, amount); // 可能失败
accountDao.addBalance(toId, amount); // 可能失败导致数据不一致
}
// 正确:添加事务管理
@Transactional // 使用Spring声明式事务
public void transferMoney(int fromId, int toId, BigDecimal amount) {
accountDao.deductBalance(fromId, amount);
accountDao.addBalance(toId, amount);
}
用户界面开发
控制台和Swing界面实现
UI开发模式
控制台界面
实现基于文本的交互界面
public class ConsoleUI {
private final UserService userService;
public ConsoleUI(UserService userService) {
this.userService = userService;
}
public void start() {
Scanner scanner = new Scanner(System.in);
boolean running = true;
while (running) {
System.out.println("1. 登录");
System.out.println("2. 注册");
System.out.println("3. 退出");
System.out.print("请选择操作: ");
int choice = scanner.nextInt();
scanner.nextLine(); // 消耗换行符
switch (choice) {
case 1:
loginProcess(scanner);
break;
case 2:
registerProcess(scanner);
break;
case 3:
running = false;
break;
}
}
}
private void loginProcess(Scanner scanner) {
// 获取用户名和密码并调用服务
}
private void registerProcess(Scanner scanner) {
// 获取用户信息并调用注册服务
}
}
Swing界面
实现图形用户界面
public class LoginFrame extends JFrame {
private final UserService userService;
private JTextField usernameField;
private JPasswordField passwordField;
public LoginFrame(UserService userService) {
this.userService = userService;
initUI();
}
private void initUI() {
setTitle("用户登录");
setSize(300, 200);
setDefaultCloseOperation(EXIT_ON_CLOSE);
JPanel panel = new JPanel(new GridLayout(3, 2, 5, 5));
panel.add(new JLabel("用户名:"));
usernameField = new JTextField();
panel.add(usernameField);
panel.add(new JLabel("密码:"));
passwordField = new JPasswordField();
panel.add(passwordField);
JButton loginButton = new JButton("登录");
loginButton.addActionListener(this::onLogin);
panel.add(loginButton);
JButton registerButton = new JButton("注册");
registerButton.addActionListener(e -> openRegisterFrame());
panel.add(registerButton);
add(panel);
}
private void onLogin(ActionEvent e) {
String username = usernameField.getText();
String password = new String(passwordField.getPassword());
try {
User user = userService.login(username, password);
// 登录成功处理
} catch (UserException ex) {
JOptionPane.showMessageDialog(this, ex.getMessage(),
"登录失败", JOptionPane.ERROR_MESSAGE);
}
}
}
功能整合与测试
集成各组件并进行系统测试
测试方法与策略
单元测试(JUnit)
测试单个方法或类的功能
public class UserServiceTest {
private UserService userService;
private UserDao userDao;
@Before
public void setUp() {
// 使用Mockito模拟DAO对象
userDao = Mockito.mock(UserDao.class);
userService = new UserServiceImpl(userDao);
}
@Test
public void testLoginSuccess() {
// 准备测试数据
String username = "testuser";
String password = "validPassword";
String hashedPassword = BCrypt.hashpw(password, BCrypt.gensalt());
// 配置模拟对象行为
Mockito.when(userDao.findByUsername(username))
.thenReturn(new User(1, username, hashedPassword));
// 执行测试
User user = userService.login(username, password);
// 验证结果
Assert.assertNotNull(user);
Assert.assertEquals(username, user.getUsername());
}
}
集成测试
测试组件协同工作
public class UserIntegrationTest {
private static DataSource dataSource;
private UserService userService;
@BeforeClass
public static void setupDataSource() {
// 初始化测试数据库
dataSource = createTestDataSource();
}
@Before
public void init() {
// 创建DAO和服务实例
UserDao userDao = new UserDaoJdbcImpl(dataSource);
userService = new UserServiceImpl(userDao);
}
@Test
public void testRegisterAndLogin() {
// 注册新用户
User newUser = new User("newuser", "new@example.com");
userService.register(newUser, "password123");
// 登录验证
User loggedIn = userService.login("newuser", "password123");
// 验证登录结果
Assert.assertNotNull(loggedIn);
Assert.assertEquals("new@example.com", loggedIn.getEmail());
}
}
功能整合流程
将各层组件组合成完整应用
public class ApplicationMain {
public static void main(String[] args) {
// 1. 初始化数据源
DataSource dataSource = setupDataSource();
// 2. 创建DAO层
UserDao userDao = new UserDaoJdbcImpl(dataSource);
// 3. 创建Service层
UserService userService = new UserServiceImpl(userDao);
// 4. 启动控制台UI
if ("console".equals(args[0])) {
ConsoleUI consoleUI = new ConsoleUI(userService);
consoleUI.start();
}
// 5. 或启动Swing UI
else if ("swing".equals(args[0])) {
SwingUtilities.invokeLater(() -> {
LoginFrame frame = new LoginFrame(userService);
frame.setVisible(true);
});
}
}
}
测试最佳实践
1. 测试金字塔:编写大量单元测试,适量集成测试,少量端到端测试
2. 使用内存数据库(如H2)替代生产数据库进行测试
3. 使用Mock对象隔离测试单元
4. 自动化测试(如使用Jenkins、GitHub Actions)
5. 测试覆盖率分析(使用JaCoCo等工具)
总结与常见错误
项目回顾与经验总结
分层架构优势
- 分离关注点,代码清晰
- 模块解耦,便于维护
- 各层可独立替换实现
- 便于团队协作分工
常见错误列表
- 数据库设计不合理,后期频繁修改表结构
- 未进行参数验证导致SQL注入或业务逻辑错误
- 事务处理不当导致数据不一致
- UI层包含业务逻辑
- 未进行足够的异常处理
进阶学习路径
- Spring Framework核心(IoC, AOP)
- Spring Boot快速开发
- Spring Data JPA替代传统DAO
- JavaFX现代化UI开发
- 容器化部署(Docker, Kubernetes)
综合练习
实现一个图书馆管理系统,包括:
- 数据库设计(读者、图书、借阅记录)
- DAO层实现CRUD操作
- Service层处理借阅/归还业务规则
- 控制台和Swing双界面
- 单元测试覆盖核心业务
提示: 考虑使用SQLite作为嵌入式数据库简化部署