用Python重写Java项目:学以致用的综合实战
摘要:本文通过一个具体的Spring Boot用户管理API项目,用Python(FastAPI)重写,对比两种实现的异同。
写在前面
前面九篇文章我们学习了Java和Python的语法差异、数据结构、函数特性、OOP、异常处理、模块管理、常用库、Web框架和并发编程。
这一课,我们用一个完整的项目来综合运用。
我们将构建一个用户管理系统API,包含:
- 用户CRUD操作
- 分页查询
- 数据验证
- 异常处理
- 单元测试
一、项目需求
1.1 Java Spring Boot版本
// User.java - 实体类
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private Integer age;
private Boolean active = true;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// getters, setters, constructors
}
// UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
List<User> findByAgeGreaterThan(Integer age);
Page<User> findByActiveTrue(Pageable pageable);
}
// UserService.java
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public User create(UserRequest request) {
if (userRepository.findByEmail(request.getEmail()).isPresent()) {
throw new BusinessException("EMAIL_EXISTS", "邮箱已存在");
}
User user = new User();
user.setName(request.getName());
user.setEmail(request.getEmail());
user.setAge(request.getAge());
user.setActive(true);
user.setCreatedAt(LocalDateTime.now());
return userRepository.save(user);
}
public User getById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new BusinessException("USER_NOT_FOUND", "用户不存在"));
}
public Page<User> list(int page, int size) {
return userRepository.findByActiveTrue(PageRequest.of(page, size));
}
public User update(Long id, UserRequest request) {
User user = getById(id);
user.setName(request.getName());
user.setEmail(request.getEmail());
user.setAge(request.getAge());
user.setUpdatedAt(LocalDateTime.now());
return userRepository.save(user);
}
public void delete(Long id) {
User user = getById(id);
user.setActive(false);
userRepository.save(user);
}
}
// UserController.java
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping
public ResponseEntity<User> create(@Valid @RequestBody UserRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(userService.create(request));
}
@GetMapping("/{id}")
public User get(@PathVariable Long id) {
return userService.getById(id);
}
@GetMapping
public Page<User> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
return userService.list(page, size);
}
@PutMapping("/{id}")
public User update(@PathVariable Long id, @Valid @RequestBody UserRequest request) {
return userService.update(id, request);
}
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) {
userService.delete(id);
}
}
// BusinessException.java
public class BusinessException extends RuntimeException {
private final String code;
private final String message;
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
}
// GlobalExceptionHandler.java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result handleBusiness(BusinessException e) {
return Result.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidation(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors()
.stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
return Result.error("VALIDATION_ERROR", message);
}
}
二、Python FastAPI版本
2.1 项目结构
python_user_api/
├── main.py # 应用入口
├── models.py # Pydantic模型
├── database.py # 数据库配置
├── schemas.py # SQLAlchemy模型
├── crud.py # CRUD操作
├── routers/
│ ├── __init__.py
│ └── users.py # 用户路由
├── exceptions.py # 自定义异常
├── dependencies.py # 依赖注入
└── tests/
├── __init__.py
└── test_users.py # 单元测试
2.2 安装依赖
pip install fastapi uvicorn sqlalchemy pydantic pytest httpx
2.3 核心代码
# exceptions.py - 自定义异常
class BusinessException(Exception):
def __init__(self, code: str, message: str):
self.code = code
self.message = message
class UserNotFoundException(BusinessException):
def __init__(self):
super().__init__("USER_NOT_FOUND", "用户不存在")
class EmailExistsException(BusinessException):
def __init__(self):
super().__init__("EMAIL_EXISTS", "邮箱已存在")
# models.py - Pydantic模型(请求/响应)
from pydantic import BaseModel, EmailStr, Field
from typing import Optional
from datetime import datetime
class UserBase(BaseModel):
email: EmailStr
name: str = Field(..., min_length=1, max_length=100)
age: int = Field(..., ge=0, le=150)
class UserCreate(UserBase):
pass
class UserUpdate(BaseModel):
email: Optional[EmailStr] = None
name: Optional[str] = Field(None, min_length=1, max_length=100)
age: Optional[int] = Field(None, ge=0, le=150)
class UserResponse(UserBase):
id: int
active: bool
created_at: datetime
updated_at: Optional[datetime] = None
class Config:
from_attributes = True
class PageResponse(BaseModel):
items: list[UserResponse]
total: int
page: int
size: int
pages: int
# database.py - 数据库配置
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "sqlite:///./users.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# schemas.py - SQLAlchemy模型
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from sqlalchemy.sql import func
from database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, nullable=False, index=True)
name = Column(String, nullable=False)
age = Column(Integer, nullable=False)
active = Column(Boolean, default=True)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, onupdate=func.now())
# crud.py - CRUD操作
from sqlalchemy.orm import Session
from sqlalchemy import and_
from typing import Optional, List
import math
from schemas import User, UserCreate, UserUpdate
def get_user(db: Session, user_id: int) -> Optional[User]:
return db.query(User).filter(
and_(User.id == user_id, User.active == True)
).first()
def get_user_by_email(db: Session, email: str) -> Optional[User]:
return db.query(User).filter(User.email == email).first()
def get_users(db: Session, page: int = 0, size: int = 10) -> tuple[List[User], int]:
query = db.query(User).filter(User.active == True)
total = query.count()
users = query.offset(page * size).limit(size).all()
return users, total
def create_user(db: Session, user: UserCreate) -> User:
db_user = User(
email=user.email,
name=user.name,
age=user.age,
active=True
)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
def update_user(db: Session, user_id: int, user: UserUpdate) -> User:
db_user = get_user(db, user_id)
if db_user is None:
return None
update_data = user.model_dump(exclude_unset=True)
for key, value in update_data.items():
setattr(db_user, key, value)
db.commit()
db.refresh(db_user)
return db_user
def delete_user(db: Session, user_id: int) -> bool:
db_user = get_user(db, user_id)
if db_user is None:
return False
db_user.active = False
db.commit()
return True
# dependencies.py - 依赖注入
from sqlalchemy.orm import Session
from database import get_db
def get_user_service(db: Session = Depends(get_db)):
return UserService(db)
class UserService:
def __init__(self, db: Session):
self.db = db
def create(self, user_data: UserCreate):
from crud import get_user_by_email, create_user
if get_user_by_email(self.db, user_data.email):
raise EmailExistsException()
return create_user(self.db, user_data)
def get_by_id(self, user_id: int):
from crud import get_user
user = get_user(self.db, user_id)
if not user:
raise UserNotFoundException()
return user
def list(self, page: int, size: int):
from crud import get_users
users, total = get_users(self.db, page, size)
pages = math.ceil(total / size) if size > 0 else 0
return {
"items": users,
"total": total,
"page": page,
"size": size,
"pages": pages
}
def update(self, user_id: int, user_data: UserUpdate):
from crud import update_user
user = update_user(self.db, user_id, user_data)
if not user:
raise UserNotFoundException()
return user
def delete(self, user_id: int):
from crud import delete_user
if not delete_user(self.db, user_id):
raise UserNotFoundException()
# routers/users.py - 用户路由
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List
from models import UserCreate, UserUpdate, UserResponse, PageResponse
from dependencies import UserService, get_user_service
router = APIRouter(prefix="/api/users", tags=["users"])
@router.post("", response_model=UserResponse, status_code=201)
def create_user(
user_data: UserCreate,
service: UserService = Depends(get_user_service)
):
try:
return service.create(user_data)
except EmailExistsException as e:
raise HTTPException(status_code=400, detail={"code": e.code, "message": e.message})
@router.get("/{user_id}", response_model=UserResponse)
def get_user(
user_id: int,
service: UserService = Depends(get_user_service)
):
try:
return service.get_by_id(user_id)
except UserNotFoundException as e:
raise HTTPException(status_code=404, detail={"code": e.code, "message": e.message})
@router.get("", response_model=PageResponse)
def list_users(
page: int = Query(default=0, ge=0),
size: int = Query(default=10, ge=1, le=100),
service: UserService = Depends(get_user_service)
):
return service.list(page, size)
@router.put("/{user_id}", response_model=UserResponse)
def update_user(
user_id: int,
user_data: UserUpdate,
service: UserService = Depends(get_user_service)
):
try:
return service.update(user_id, user_data)
except UserNotFoundException as e:
raise HTTPException(status_code=404, detail={"code": e.code, "message": e.message})
@router.delete("/{user_id}", status_code=204)
def delete_user(
user_id: int,
service: UserService = Depends(get_user_service)
):
try:
service.delete(user_id)
except UserNotFoundException as e:
raise HTTPException(status_code=404, detail={"code": e.code, "message": e.message})
# main.py - 应用入口
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from database import engine, Base
from routers.users import router as users_router
from exceptions import BusinessException
Base.metadata.create_all(bind=engine)
app = FastAPI(title="User Management API", version="1.0.0")
app.include_router(users_router)
@app.exception_handler(BusinessException)
async def handle_business_exception(request: Request, exc: BusinessException):
return JSONResponse(
status_code=400,
content={"code": exc.code, "message": exc.message}
)
@app.exception_handler(RequestValidationError)
async def handle_validation_exception(request: Request, exc: RequestValidationError):
errors = exc.errors()
message = ", ".join([f"{e['loc']}: {e['msg']}" for e in errors])
return JSONResponse(
status_code=422,
content={"code": "VALIDATION_ERROR", "message": message}
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
三、单元测试对比
3.1 Java JUnit测试
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void testCreateUser() {
UserRequest request = new UserRequest("alice@example.com", "Alice", 30);
when(userRepository.findByEmail("alice@example.com"))
.thenReturn(Optional.empty());
User savedUser = new User(1L, "alice@example.com", "Alice", 30, true);
when(userRepository.save(any(User.class))).thenReturn(savedUser);
User result = userService.create(request);
assertNotNull(result);
assertEquals("Alice", result.getName());
verify(userRepository).findByEmail("alice@example.com");
verify(userRepository).save(any(User.class));
}
@Test
void testGetUserNotFound() {
when(userRepository.findById(999L)).thenReturn(Optional.empty());
BusinessException ex = assertThrows(
BusinessException.class,
() -> userService.getById(999L)
);
assertEquals("USER_NOT_FOUND", ex.getCode());
}
}
3.2 Python pytest测试
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool
from main import app
from database import Base, get_db
from schemas import User
from crud import create_user, get_user_by_email
# 测试数据库
SQLALCHEMY_DATABASE_URL = "sqlite:///:memory:"
engine = create_engine(SQLALCHEMY_DATABASE_URL,
connect_args={"check_same_thread": False},
poolclass=StaticPool)
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def override_get_db():
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_get_db
@pytest.fixture(autouse=True)
def setup_database():
Base.metadata.create_all(bind=engine)
yield
Base.metadata.drop_all(bind=engine)
client = TestClient(app)
class TestUserAPI:
def test_create_user(self):
response = client.post("/api/users", json={
"email": "alice@example.com",
"name": "Alice",
"age": 30
})
assert response.status_code == 201
data = response.json()
assert data["email"] == "alice@example.com"
assert data["name"] == "Alice"
assert data["active"] is True
def test_create_user_duplicate_email(self):
client.post("/api/users", json={
"email": "alice@example.com",
"name": "Alice",
"age": 30
})
response = client.post("/api/users", json={
"email": "alice@example.com",
"name": "Bob",
"age": 25
})
assert response.status_code == 400
assert response.json()["detail"]["code"] == "EMAIL_EXISTS"
def test_get_user_not_found(self):
response = client.get("/api/users/999")
assert response.status_code == 404
assert response.json()["detail"]["code"] == "USER_NOT_FOUND"
def test_list_users(self):
for i in range(5):
client.post("/api/users", json={
"email": f"user{i}@example.com",
"name": f"User{i}",
"age": 20 + i
})
response = client.get("/api/users?page=0&size=2")
assert response.status_code == 200
data = response.json()
assert len(data["items"]) == 2
assert data["total"] == 5
assert data["pages"] == 3
def test_update_user(self):
create_resp = client.post("/api/users", json={
"email": "alice@example.com",
"name": "Alice",
"age": 30
})
user_id = create_resp.json()["id"]
response = client.put(f"/api/users/{user_id}", json={
"name": "Alice Updated"
})
assert response.status_code == 200
assert response.json()["name"] == "Alice Updated"
def test_delete_user(self):
create_resp = client.post("/api/users", json={
"email": "alice@example.com",
"name": "Alice",
"age": 30
})
user_id = create_resp.json()["id"]
response = client.delete(f"/api/users/{user_id}")
assert response.status_code == 204
get_resp = client.get(f"/api/users/{user_id}")
assert get_resp.status_code == 404
四、运行测试
4.1 运行Python测试
cd python_user_api
pytest tests/ -v
4.2 启动服务器
uvicorn main:app --reload
4.3 API文档
FastAPI自动生成API文档:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
五、代码行数对比
| 模块 | Java (行) | Python (行) |
|---|---|---|
| 实体/模型 | ~50 | ~25 (Pydantic) |
| 仓库/CRUD | ~60 | ~80 |
| 服务层 | ~50 | ~40 |
| 控制器/路由 | ~40 | ~50 |
| 异常处理 | ~30 | ~15 |
| 测试 | ~60 | ~80 |
| 总计 | ~290 | ~290 |
Python的优势:
- 无需getter/setter(Pydantic自动生成)
- 无需编译
- 测试代码更直观
六、核心差异总结
| 维度 | Java Spring Boot | Python FastAPI |
|---|---|---|
| 启动方式 | mvn spring-boot:run | uvicorn main:app |
| 数据验证 | @Valid + 注解 | Pydantic模型 |
| 路由 | @RequestMapping | @router.post() |
| 依赖注入 | @Autowired | Depends() |
| ORM | JPA/Hibernate | SQLAlchemy |
| 序列化 | Jackson | Pydantic |
| 文档 | SpringDoc | 自动生成 |
| 测试 | JUnit + MockMvc | pytest + TestClient |
七、延伸学习建议
7.1 下一步可以学习的
-
数据库迁移
- Java: Flyway / Liquibase
- Python: Alembic
-
认证授权
- Java: Spring Security / OAuth2
- Python: FastAPI-security / JWT
-
API文档
- Java: SpringDoc OpenAPI
- Python: FastAPI内置 + openapi-schema-pydantic
-
性能优化
- Java: JPA二级缓存 / Redis
- Python: SQLAlchemy缓存 / Redis
-
部署
- Java: JAR + Docker
- Python: Gunicorn + Docker
7.2 推荐项目
- 用Django重写(对比全功能框架)
- 用Flask重写(对比微框架)
- 添加Redis缓存
- 添加单元测试覆盖率
八、系列总结
经过十篇文章的学习,你应该已经:
| 课程 | 核心知识点 |
|---|---|
| 01 | 基础语法对比、缩进、注释 |
| 02 | 数据结构(list/dict/set vs ArrayList/HashMap) |
| 03 | 函数、lambda、装饰器、生成器 |
| 04 | 类、继承、多继承、dataclass |
| 05 | 异常处理、try-except、with语句 |
| 06 | import机制、pip、虚拟环境 |
| 07 | 文件IO、JSON、正则、datetime |
| 08 | Flask/Django/FastAPI框架对比 |
| 09 | GIL、多线程、多进程、asyncio |
| 10 | 实战项目综合运用 |
恭喜你完成了"Java工程师的Python学习之路"系列!
继续加油。技术的道路永无止境,Java和Python各有优势,两者结合会更加游刃有余。