1. JDBC外键处理实战解析
在Java企业级开发中,数据库关系映射是核心技能之一。外键作为关系型数据库的重要特性,其处理方式直接影响着数据完整性和业务逻辑的正确性。下面我将通过一个员工-部门管理系统案例,详细讲解JDBC中外键的面向对象处理方式。
1.1 实体类设计技巧
在员工实体类(Employee)中,我们采用了面向对象的方式处理外键关系:
java复制public class Employee {
private String emp_no;
private String emp_name;
// 其他基础字段...
private Departement departement; // 外键关联的部门对象
// 构造器、getter/setter省略...
}
这里的关键设计点在于:
- 不使用原始的外键字段(如dep_no),而是直接关联部门对象
- 通过对象引用的方式建立关系,更符合面向对象思想
- 便于后续扩展部门信息的获取和操作
提示:这种设计模式称为"对象关系映射"(ORM)的基础实现,虽然简单但体现了ORM的核心思想
1.2 DAO层实现细节
在EmployeeDaoImpl中,外键处理主要体现在SQL操作上:
java复制public int insert(Employee employee) {
String sql = "insert into employee(emp_no, emp_name, ..., dep_no) values(?,?,...,?)";
pst.setString(1, employee.getEmp_no());
// 其他字段设置...
pst.setInt(5, employee.getDepartement().getDep_no()); // 获取部门对象中的编号
}
查询操作时,需要特别注意结果集到对象的转换:
java复制while (rs.next()) {
Employee employee = new Employee();
// 设置基础字段...
// 处理外键关系
Departement d = new Departement();
d.setDep_no(rs.getInt("dep_no")); // 从结果集获取外键值
employee.setDepartement(d); // 设置部门对象
}
1.3 服务层与测试案例
服务层(Service)作为业务逻辑的封装,主要职责是协调DAO操作:
java复制public class EmployeeServiceImpl implements EmployeeService {
private EmployeeDao employeeDao = new EmployeeDaoImpl();
public boolean add(Employee employee) {
return employeeDao.insert(employee) > 0;
}
// 其他方法...
}
测试案例展示了完整的CRUD操作流程,特别是外键关联数据的处理:
java复制// 添加员工时处理部门外键
System.out.println("请输入部门编号:");
int depno = input.nextInt();
departement.setDep_no(depno); // 先设置部门对象
employee.setDepartement(departement); // 再关联到员工
2. JDBC日期时间处理详解
日期时间是业务系统中另一常见复杂类型,JDBC中需要特别注意java.util.Date与java.sql.Date的转换问题。
2.1 日期转换工具类
建议封装专门的日期转换工具:
java复制public class DateOrString {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public static Date strToDate(String str) {
try {
return sdf.parse(str);
} catch (ParseException e) {
throw new RuntimeException("日期格式转换异常", e);
}
}
public static String dateToStr(Date date) {
return sdf.format(date);
}
}
注意:SimpleDateFormat不是线程安全的,在Web应用中建议使用ThreadLocal或每次创建新实例
2.2 实体类中的日期字段
学生实体中使用java.util.Date类型:
java复制public class Student {
private Date stu_born; // 出生日期
// getter/setter...
}
2.3 DAO层日期处理
在DAO实现中,需要处理java.util.Date到java.sql.Date的转换:
java复制public int insert(Student student) {
String sql = "insert into student(..., stu_born) values(...,?)";
// 其他参数设置...
pst.setDate(8, new java.sql.Date(student.getStu_born().getTime()));
}
查询时的处理相对简单,因为ResultSet.getDate()返回的就是java.sql.Date:
java复制while(rs.next()) {
Student stu = new Student();
stu.setStu_born(rs.getDate("stu_born"));
// ...
}
2.4 测试案例中的日期输入
测试类展示了两种日期输入处理方式:
- 直接使用SimpleDateFormat
java复制SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date stu_born = sdf.parse(input.next());
- 使用工具类
java复制student.setStu_born(DateOrString.strToDate(input.next()));
3. 实战经验与常见问题
3.1 外键处理的坑与技巧
- 空指针问题:关联对象未初始化
java复制// 错误示范
Departement departement = null; // 会导致NPE
employee.setDepartement(departement);
// 正确做法
Departement departement = new Departement();
departement.setDep_no(depNo);
employee.setDepartement(departement);
- 懒加载问题:只设置了外键ID,未加载完整对象
java复制// 只设置部门ID
Departement d = new Departement();
d.setDep_no(rs.getInt("dep_no"));
employee.setDepartement(d);
// 如果需要部门详细信息,应该查询部门表获取完整信息
- 事务处理:关联操作需要放在同一事务中
java复制Connection con = null;
try {
con = dataSource.getConnection();
con.setAutoCommit(false); // 开启事务
// 先插入部门
departmentDao.insert(department, con);
// 再插入员工
employeeDao.insert(employee, con);
con.commit();
} catch (SQLException e) {
if(con != null) con.rollback();
throw e;
} finally {
if(con != null) con.close();
}
3.2 日期处理的注意事项
- 时区问题:
java复制// 明确指定时区
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
- 日期格式验证:
java复制// 添加格式验证
public static Date strToDate(String str) throws ParseException {
sdf.setLenient(false); // 严格模式
return sdf.parse(str);
}
- Java 8+的日期API:
java复制// 更推荐使用java.time包
private LocalDate stu_born;
// 转换方法
public static LocalDate strToLocalDate(String str) {
return LocalDate.parse(str, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
4. 架构优化建议
4.1 分层架构改进
- DTO与VO分离:
java复制// 数据传输对象
public class EmployeeDTO {
private String empNo;
private String empName;
private Integer depNo; // 使用简单类型表示关联
}
// 视图对象
public class EmployeeVO {
private String empNo;
private String empName;
private String depName; // 包含关联对象信息
}
- DAO接口通用化:
java复制public interface BaseDao<T> {
int insert(T t);
int delete(ID id);
int update(T t);
T selectById(ID id);
List<T> selectAll();
}
4.2 使用连接池
- DBCP配置示例:
java复制public class DbcpUtil {
private static DataSource dataSource;
static {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/test");
ds.setUsername("root");
ds.setPassword("123456");
ds.setInitialSize(5);
ds.setMaxActive(10);
dataSource = ds;
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}
4.3 使用预编译语句
java复制public class EmployeeDaoImpl implements EmployeeDao {
private static final String INSERT_SQL = "INSERT INTO employee(...) VALUES(...)";
private static final String UPDATE_SQL = "UPDATE employee SET ... WHERE ...";
public int insert(Employee employee) {
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(INSERT_SQL)) {
// 设置参数...
return pstmt.executeUpdate();
}
}
}
5. 扩展思考
5.1 一对多关系处理
部门对员工的一对多关系实现:
java复制public class Department {
private Integer depNo;
private String depName;
private List<Employee> employees;
// getter/setter...
}
// 查询方法
public Department getDepartmentWithEmployees(Integer depNo) {
Department dept = departmentDao.selectById(depNo);
dept.setEmployees(employeeDao.selectByDeptNo(depNo));
return dept;
}
5.2 使用注解简化ORM
自定义注解简化映射:
java复制@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String name();
boolean isId() default false;
boolean isForeignKey() default false;
}
// 应用示例
public class Employee {
@Column(name = "emp_no", isId = true)
private String empNo;
@Column(name = "dep_no", isForeignKey = true)
private Integer depNo;
}
5.3 使用JPA/Hibernate
现代Java持久化方案示例:
java复制@Entity
@Table(name = "employee")
public class Employee {
@Id
private String empNo;
private String empName;
@ManyToOne
@JoinColumn(name = "dep_no")
private Department department;
// getter/setter...
}
在实际项目开发中,根据项目规模和团队技术栈,可以选择从原生JDBC逐步过渡到成熟的ORM框架。但理解这些底层原理对于处理复杂数据库操作和性能优化至关重要。