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<String>
 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<String> {

 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<Machine> 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<Account> getAccounts() {
 return accounts;
 }

 public void setAccounts(List<Account> accounts) {
 this.accounts = accounts;
 }
 // setters and getters omitted ...

} // 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<String> {

 private static final long serialVersionUID = 3832626162173359411L;
 private List<Application> applications = new LinkedList<Application>();
 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<Application> getApplications() {
 return applications;
 }

 public void setApplications(List<Application> Applications) {
 this.applications = applications;
 }
 // setters and getters omitted ...

} // 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<Account, AccountPK> 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;

more to come….

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…

  • Share/Bookmark

This entry was posted on Wednesday, November 25th, 2009 at 7:18 am and is filed under Java. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

 

Leave a Reply

You must be logged in to post a comment.