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…

Mick Knutson

Java, JavaEE, J2EE, WebLogic, WebSphere, JBoss, Tomcat, Oracle, Spring, Maven, Architecture, Design, Mentoring, Instructor and Agile Consulting. http://www.baselogic.com/blog/resume

View all posts

Java / JavaEE / Spring Boot Channel

BLiNC Supporters

BLiNC Adsense

Archives

Newsletter