Composite Primary Key pattern in Hibernate
I was trying to create a relationship without using surrogate primary keys the other day in Hibernate. I started searching around for examples, but did not find many examples that used Annotations.
So here is what I implemented:
I have an Application that can have many Accounts spread across many Machines. Each Machine can only have 1 Account name per Application on a given Machine. Thus, there will be many Accounts, but only 1 Account per Application.
So I first create an AccountPK as my composite Primary Key:
package com.fedex.ground.scm.pmi.domain;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class AccountPK implements Serializable {
private static final long serialVersionUID = -849734046114464605L;
public AccountPK() {
}
public AccountPK(String id, String applicationId) {
this.id = id;
this.applicationId = applicationId;
}
private String id;
private String applicationId;
@Column(name = "id")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Column(name = "application_fk")
public String getApplicationId() {
return applicationId;
}
public void setApplicationId(String applicationId) {
this.applicationId = applicationId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AccountPK accountPK = (AccountPK) o;
if (id != null ? !id.equals(accountPK.id) : accountPK.id != null) return false;
if (applicationId != null ? !applicationId.equals(accountPK.applicationId) : accountPK.applicationId != null)
return false;
return true;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (applicationId != null ? applicationId.hashCode() : 0);
return result;
}
} // The End...
Then I created my Account Object:
package com.fedex.ground.scm.pmi.domain;
import javax.persistence.*;
import com.baselogic.domain.IdentifiedObjectListener;
import com.baselogic.domain.StringIdentifiedObject;
import org.hibernate.annotations.Type;
import java.io.Serializable;
@Entity
@Table(name = "application_account")
@IdClass(AccountPK.class)
@EntityListeners({IdentifiedObjectListener.class})
public class Account
extends StringIdentifiedObject
implements Serializable {
private String id;
private String applicationId;
private static final long serialVersionUID = 3832626162173359411L;
private AccountPK accountPK;
private Application application;
private String password;
private String previousPassword;
private Boolean inSafe;
private String environment;
private String status;
public Account() {
super();
}
@Id
@Column(name = "application_fk", insertable = false, updatable = false)
public String getApplicationId() {
return applicationId;
}
public void setApplicationId(String applicationId) {
this.applicationId = applicationId;
}
@ManyToOne
@JoinColumn(name = "application_fk", insertable = false, updatable = false)
public Application getApplication() {
return application;
}
public void setApplication(Application application) {
this.application = application;
}
// setters and getters omitted ...
}// The End...
Then created my Application Object:
package com.fedex.ground.scm.pmi.domain;
import java.util.LinkedList;
import java.util.List;
import javax.persistence.*;
import org.hibernate.annotations.*;
import com.baselogic.domain.StringIdentifiedObject;
@Entity
@Table(name = "application")
public class Application extends StringIdentifiedObject {
private static final long serialVersionUID = 3832626162173359411L;
private List<Machine>machines=new LinkedList<Machine>();
private List<Account>accounts=new LinkedList<Account>();
private String ip;
private String alias;
private String status;
@ManyToMany(cascade = CascadeType.MERGE)
@JoinTable(name = "application_machine",
joinColumns = {@JoinColumn(name = "application_id")},
inverseJoinColumns = {@JoinColumn(name = "machine_id")})
@Fetch(FetchMode.SUBSELECT)
public List
getMachines() {
return machines;
}
public void setMachines(List<Machine>machines) {
this.machines = machines;
}
@OneToMany(mappedBy = "application", cascade = CascadeType.MERGE)
@JoinColumn(name = "application_fk", insertable = true, updatable = false, nullable = true)
@Fetch(FetchMode.SUBSELECT)
public List
getAccounts() {
return accounts;
}
public void setAccounts(Listaccounts) {
this.accounts = accounts;
}
//<em> // setters and getters omitted ...</em>
} // The End...
Then created my Machine Object:
package com.fedex.ground.scm.pmi.domain;
import java.util.LinkedList;
import java.util.List;
import javax.persistence.*;
import org.hibernate.annotations.*;
import com.baselogic.domain.StringIdentifiedObject;
@Entity
@Table(name = "machine")
public class Machine extends StringIdentifiedObject {
private static final long serialVersionUID = 3832626162173359411L;
private List applications = new LinkedList();
private String ip;
private String alias;
private String machineType;
private String status;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "application_machine",
joinColumns = {@JoinColumn(name = "machine_id")},
inverseJoinColumns = {@JoinColumn(name = "application_id")})
@Fetch(FetchMode.SUBSELECT)
public List getApplications() {
return applications;
}
public void setApplications(List Applications) {
this.applications = applications;
}
//<em> // setters and getters omitted ...</em>
} // The End...
Then I created my AccountTest Class:
package com.fedex.ground.scm.pmi.domain;
import java.util.List;
import com.baselogic.dao.BaseDao;
import com.fedex.ground.scm.pmi.domain.Account;
import org.springframework.beans.factory.*;
import org.springframework.test.context.ContextConfiguration;
import org.junit.Test;
import static org.junit.Assert.*;
@ContextConfiguration(locations = {"classpath:applicationContext-test.xml"})
public class AccountTest
extends com.baselogic.test.AbstractHibernateJUnit4Test
implements InitializingBean {
@Autowired
@Qualifier("accountDao")
private BaseDao dao;
@Test
public void testFindAccountObject() {
AccountPK pk = new AccountPK("scmbld", "pmi2");
Account account = dao.find(pk);
assertNotNull(account);
assertEquals("scmbld", account.getId());
assertEquals("password-here", account.getPassword());
}
} // The End...
This works great and generates the needed DDL. I have tested this with H2 as well as MySql databases.
create table application ( id varchar(255) not null, date_created timestamp not null, date_updated timestamp not null, alias varchar(255), ip varchar(255), status varchar(255), primary key (id) ); create table application_account ( application_fk varchar(255) not null, id varchar(255) not null, date_created timestamp not null, date_updated timestamp not null, environment varchar(25), inSafe char(1), password varchar(50) not null, previousPassword varchar(50), status varchar(10), primary key (application_fk, id) ); create table application_machine ( machine_id varchar(255) not null, application_id varchar(255) not null ); create table machine ( id varchar(255) not null, date_created timestamp not null, date_updated timestamp not null, alias varchar(255), ip varchar(255), machineType varchar(255), status varchar(255), primary key (id) ); alter table application_account add constraint FK6A94E63EF4253111 foreign key (application_fk) references application; alter table application_machine add constraint FKE1F2A058F4253167 foreign key (application_id) references application; alter table application_machine add constraint FKE1F2A0582DDA9887 foreign key (machine_id) references machine;
My JUnit Tests
package com.fedex.ground.scm.pmi.service;
import static com.baselogic.dao.criteria.Comparison.*;
import com.baselogic.dao.criteria.Group;
import com.fedex.ground.scm.pmi.PmiException;
import com.fedex.ground.scm.pmi.domain.Account;
import com.fedex.ground.scm.pmi.domain.AccountPK;
import com.fedex.ground.scm.pmi.domain.Application;
import com.fedex.ground.scm.pmi.service.AccountService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import java.util.List;
import static org.junit.Assert.*;
import org.junit.Assert;
import org.junit.Test;
/**
* User DAO Test.
*
* @author $author$
* @version $Revision$, $Date$
*/
@ContextConfiguration(locations = {"classpath:applicationContext-test.xml"})
public class AccountServiceTest
extends com.baselogic.test.AbstractHibernateJUnit4Test
implements InitializingBean {
@Autowired
@Qualifier("accountService")
private AccountService service;
// ===== Start the Unit Tests ============================================//
@Test
public void testFindAllObjects() {
List<Account> accounts = service.findAll();
assertNotNull(accounts);
assertTrue(accounts.size() >= 1);
}
@Test
public void testFindById() {
AccountPK pk = new AccountPK("scmbld", "pmi2");
Account account = service.find(pk);
assertNotNull(account);
Application application = account.getApplication();
assertNotNull(application);
assertEquals("pmi2", application.getId());
}
@Test
public void testAddNewAccount() {
Account account = new Account();
account.setPassword("pmi2-password");
account.setId("testAccount");
account.setApplicationId("pmi2");
Account account2 = service.create(account);
// Ensure the StringIdentifiedObjectListener sets
// the following fields:
assertNotNull(account2);
assertNotNull(account2.getId());
assertNotNull(account2.getDateCreated());
assertNotNull(account2.getDateUpdated());
assertNotNull(account2.getApplication());
}
@Test
public void testUpdateAccount() {
AccountPK pk = new AccountPK("scmbld", "pmi2");
Account account = service.find(pk);
assertNotNull(account);
// Change password
assertEquals("password-here", account.getPassword());
account.setPassword("new-password");
service.update(account);
assertEquals("new-password", account.getPassword());
// Change Application
Application application = account.getApplication();
assertNotNull(application);
assertEquals("pmi2", application.getId());
try {
account = service.update("2", account);
} catch (PmiException e) {
fail("Should have a valid Application and not fail here.");
}
application = account.getApplication();
assertNotNull(application);
assertEquals("2", application.getId());
}
@Test
public void testUpdateAccountBadApplication() {
AccountPK pk = new AccountPK("scmbld", "pmi2");
Account account = service.find(pk);
assertNotNull(account);
try {
account = service.update("BAD_APPLICATION", account);
fail("Should have failed due to BAD_APPLIATION.");
} catch (PmiException e) {
assertNotNull(e);
}
}
/**
* Mass Update accounts
* TODO: Need to test this
*/
//@Test
public void testMassUpdateAccount() {
AccountPK pk = new AccountPK("scmbld", "pmi2");
Account account = service.find(pk);
assertNotNull(account);
// Change password
assertEquals("password-here", account.getPassword());
account.setPassword("new-password");
service.update(account);
assertEquals("new-password", account.getPassword());
// Change Application
Application application = account.getApplication();
assertNotNull(application);
assertEquals("pmi2", application.getId());
try {
account = service.update("2", account);
} catch (PmiException e) {
fail("Should have a valid Application and not fail here.");
}
application = account.getApplication();
assertNotNull(application);
assertEquals("2", application.getId());
}
//===== Search Tests ====================================================//
@Test
public void testFindByApplicationId() {
try {
List<Account> accounts = service.findBy(null, "pmi2", null);
assertNotNull(accounts);
assertEquals(2, accounts.size());
} catch (PmiException e) {
fail();
}
}
@Test
public void testFindLikeApplicationId() {
try {
List<Account> accounts = service.findBy(null, "%pm%", null);
assertNotNull(accounts);
assertEquals(2, accounts.size());
} catch (PmiException e) {
fail();
}
}
@Test
public void testFindLikeApplicationIdPre() {
try {
List<Account> accounts = service.findBy(null, "%pm", null);
assertNotNull(accounts);
assertEquals(0, accounts.size());
} catch (PmiException e) {
fail();
}
}
@Test
public void testFindLikeApplicationIdPost() {
try {
List<Account> accounts = service.findBy(null, "pm%", null);
assertNotNull(accounts);
assertEquals(2, accounts.size());
} catch (PmiException e) {
fail();
}
}
@Test
public void testFindLikeApplicationIdNoResults() {
try {
List<Account> accounts = service.findBy(null, "%BAD%", null);
assertNotNull(accounts);
assertEquals(0, accounts.size());
} catch (PmiException e) {
fail();
}
}
@Test
public void testFindByApplicationIdAndAccountId() {
try {
List<Account> accounts = service.findBy(null, "pmi2", "scmbld");
assertNotNull(accounts);
assertEquals(1, accounts.size());
} catch (PmiException e) {
fail();
}
}
@Test
public void testFindByApplicationIdAndAccountId2() {
try {
List<Account> accounts = service.findBy(null, "%pmi", "scmbld");
assertNotNull(accounts);
assertEquals(0, accounts.size());
} catch (PmiException e) {
fail();
}
}
@Test
public void testFindByApplicationIdAndAccountId3() {
try {
List<Account> accounts = service.findBy(null, "pmi%", "scmbld");
assertNotNull(accounts);
assertEquals(1, accounts.size());
} catch (PmiException e) {
fail();
}
}
@Test
public void testFindByApplicationIdAndAccountId4() {
try {
List<Account> accounts = service.findBy(null, "pmi%", "%scm");
assertNotNull(accounts);
assertEquals(0, accounts.size());
} catch (PmiException e) {
fail();
}
}
@Test
public void testFindByApplicationIdAndAccountId5() {
try {
List<Account> accounts = service.findBy(null, "pmi%", "scm%");
assertNotNull(accounts);
assertEquals(1, accounts.size());
} catch (PmiException e) {
fail();
}
}
} // The End...
Conclusion
to be written…






