我们都熟知,当合同到期后需要同公司进行合同续签,如果合同信息有变化或者内容有误等都需要进行合同变
更。为了保留历史数据,公司的做法一般都是封版现有生效的合同然后插入新的合同信息,这样每个员工都有一条或
者多条合同信息。在这种应用场景下一个主键id已经不能满足需求了,这时候我们可以考虑给它提供另外一个标识信
息来过滤当前有效信息,这样由多个字段组合成的主键即复合主键。这里假设以员工id和合同起始日期为主键来进行
讲解。
复合主键映射,通常需要将主键相关字段放到一个单独的类中进行维护。对于复合主键类需要满足两个要求:
* 1.实现序列化接口
* 2.覆盖equals和hashcode方法
equals方法用于判断传入的对象是否相同,EntityManager通过find方法来查找Entity时,是根据equals方法的
返回值来判断的。而为了保证该类可以结合所有基于散列的集合一起正常工作,同时需要覆盖hashcode方法。
下面结合源码解析一步步进行讲解。复合主键类:
import java.io.Serializable;
import java.util.Date;
/**
* 员工合同复合主键类
* 1.实现序列化接口
* 2.覆盖equals和hashcode方法
* @author WYQ
*
*/
public class ContractPK implements Serializable {
//员工code
private String employeeCode;
//合同开始日期
private Date startDate;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((employeeCode == null) ? 0 : employeeCode.hashCode());
result = prime * result
+ ((startDate == null) ? 0 : startDate.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ContractPK other = (ContractPK) obj;
if (employeeCode == null) {
if (other.employeeCode != null)
return false;
} else if (!employeeCode.equals(other.employeeCode))
return false;
if (startDate == null) {
if (other.startDate != null)
return false;
} else if (!startDate.equals(other.startDate))
return false;
return true;
}
//getter{...} setter{...}
}
员工合同类,需要将上面定义的复合主键类注入到该主体类中:
import java.util.Date;
/**
* 员工合同类
* @author WYQ
*
*/
public class EmployeeContract {
//注入复合主键
private ContractPK contractPK;
//合同类型,1.首签 2.续签 3.变更
private int contractType;
//合同详情
private String content;
//合同截止日期
private Date endDate;
//getter{...} setter{...}
}
</span></span>
将实体类的映射信息添加到EmployeeContract.hbm.xml配置文件中。需要注意的是,这里主键标识不再是id,id
是用来标识单一主键的标签,而composite-id是用来标识复合主键的标签,将复合主键中字段都添加到配置文件中。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.bjpowernode.hibernate.EmployeeContract" table="t_employeeContract">
<composite-id name="contractPK">
<key-property name="employeeCode"/>
<key-property name="startDate"/>
</composite-id>
<property name="contractType" type="int"/>
<property name="content" type="string"/>
<property name="endDate" type="date"/>
</class>
</hibernate-mapping>
接下来就是将映射文件添加到hibernate配置文件中,连接数据库了。
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/employeeContract</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">123456</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.show_sql">true</property>
<mapping resource="com/bjpowernode/hibernate/EmployeeContract.hbm.xml"/>
</session-factory>
</hibernate-configuration>
执行mysql命令创建数据库,数据库名称即hibernate配置文件中配置的employeeContract。
创建好数据库,接下来就是创建表了。执行hibernate导出数据库脚本工具类:
import org.hibernate.cfg.Configuration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
/**
* hibernate导出数据库工具类
* @author Administrator
*
*/
public class ExportDB {
public static void main(String[] args) {
//默认读取hibernate.cfg.xml文件
Configuration cfg = new Configuration().configure();
SchemaExport export = new SchemaExport(cfg);
export.create(true, true);
}
}
在控制台中我们可以看到hibernate自动生成的创建表的语句,其中复合主键均满足非空条件。
最后创建一个单元测试类来测试一下我们的复合主键应用。
import java.text.SimpleDateFormat;
import java.util.Date;
import junit.framework.TestCase;
import org.hibernate.Session;
public class CompositePKMappingTest extends TestCase {
/**
* 添加一条合同信息
*/
public void testSave1() {
Session session = null;
try {
session = HibernateUtils.getSession();
session.beginTransaction();
//实例化合同对象
EmployeeContract empContract=new EmployeeContract();
//初始化复合主键
ContractPK contractPK =new ContractPK();
contractPK.setEmployeeCode("0001");
contractPK.setStartDate(new Date());
//将主键添加到合同对象
empContract.setContractPK(contractPK);
//初始化普通属性
empContract.setContent("员工test自愿与复合主键测试公司签订劳动合同,即日起生效!");
empContract.setContractType(1);
empContract.setEndDate(new Date());
session.save(empContract);
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
运行该测试方法,hibernate自动生成的sql语句如下:
<span style="font-family:KaiTi_GB2312;font-size:18px;">Hibernate: insert into t_employeeContract (contractType, content, endDate, employeeCode, startDate) values (?, ?, ?, ?, ?)</span>
复合主键最明显的优点就是比较直观,在查询的时候写sql语句会轻松些,在不需要有很多关联的表可以使用。
但是复合主键的应用也会带来很多负面影响,例如:
*1.增加了表之间的耦合性
*2.存在严重的数据冗余
*3.对用户更新数据的限制大大提高
* ...
总之,对于复合键来说,其缺点大于优点,故此,数据库设计规范中都建议避免使用复合键!