Skip to content

Commit eb2af85

Browse files
author
Igor Polevoy
committed
#405 Implement minimal support for composite primary keys
1 parent 6783250 commit eb2af85

File tree

13 files changed

+105
-89
lines changed

13 files changed

+105
-89
lines changed

activejdbc/src/main/java/org/javalite/activejdbc/MetaModel.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public class MetaModel implements Serializable {
3535
private Map<String, ColumnMetadata> columnMetadata;
3636
private final List<Association> associations = new ArrayList<Association>();
3737
private final String idName;
38-
private final String[] idCompositeKeys;
38+
private final String[] compositeKeys;
3939
private final String tableName, dbType, dbName;
4040
private final Class<? extends Model> modelClass;
4141
private final boolean cached;
@@ -46,7 +46,7 @@ public class MetaModel implements Serializable {
4646
protected MetaModel(String dbName, Class<? extends Model> modelClass, String dbType) {
4747
this.modelClass = modelClass;
4848
this.idName = findIdName(modelClass);
49-
this.idCompositeKeys = findIdCompositeKeys(modelClass);
49+
this.compositeKeys = findCompositeKeys(modelClass);
5050
this.tableName = findTableName(modelClass);
5151
this.dbType = dbType;
5252
this.cached = isCached(modelClass);
@@ -64,9 +64,9 @@ private String findIdName(Class<? extends Model> modelClass) {
6464
return idNameAnnotation == null ? "id" : idNameAnnotation.value();
6565
}
6666

67-
private String[] findIdCompositeKeys(Class<? extends Model> modelClass) {
68-
IdCompositeKeys idCompositeKeysAnnotation = modelClass.getAnnotation(IdCompositeKeys.class);
69-
return idCompositeKeysAnnotation == null ? null : idCompositeKeysAnnotation.value();
67+
private String[] findCompositeKeys(Class<? extends Model> modelClass) {
68+
CompositePK compositeKeysAnnotation = modelClass.getAnnotation(CompositePK.class);
69+
return compositeKeysAnnotation == null ? null : compositeKeysAnnotation.value();
7070
}
7171

7272
private String findTableName(Class<? extends Model> modelClass) {
@@ -207,8 +207,8 @@ public String getIdName() {
207207
*
208208
* @return composite primary key class
209209
*/
210-
public String[] getIdCompositeKeys() {
211-
return idCompositeKeys;
210+
public String[] getCompositeKeys() {
211+
return compositeKeys;
212212
}
213213

214214
/**

activejdbc/src/main/java/org/javalite/activejdbc/Model.java

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,19 @@
1515
*/
1616
package org.javalite.activejdbc;
1717

18-
import org.javalite.activejdbc.annotations.IdCompositeKeys;
18+
import org.javalite.activejdbc.annotations.CompositePK;
1919
import org.javalite.activejdbc.associations.*;
2020
import org.javalite.activejdbc.cache.QueryCache;
21+
import org.javalite.activejdbc.conversion.BlankToNullConverter;
2122
import org.javalite.activejdbc.conversion.Converter;
23+
import org.javalite.activejdbc.conversion.ZeroToNullConverter;
24+
import org.javalite.activejdbc.dialects.Dialect;
2225
import org.javalite.activejdbc.validation.NumericValidationBuilder;
2326
import org.javalite.activejdbc.validation.ValidationBuilder;
2427
import org.javalite.activejdbc.validation.ValidationException;
2528
import org.javalite.activejdbc.validation.Validator;
2629
import org.javalite.common.Convert;
30+
import org.javalite.common.Escape;
2731
import org.slf4j.Logger;
2832
import org.slf4j.LoggerFactory;
2933
import org.w3c.dom.Document;
@@ -32,7 +36,6 @@
3236
import org.w3c.dom.NodeList;
3337

3438
import javax.xml.parsers.DocumentBuilderFactory;
35-
3639
import java.io.*;
3740
import java.math.BigDecimal;
3841
import java.sql.Clob;
@@ -41,13 +44,9 @@
4144
import java.text.DateFormat;
4245
import java.util.*;
4346

44-
import org.javalite.activejdbc.conversion.BlankToNullConverter;
45-
import org.javalite.activejdbc.conversion.ZeroToNullConverter;
46-
import org.javalite.activejdbc.dialects.Dialect;
47-
import org.javalite.common.Escape;
48-
4947
import static org.javalite.common.Inflector.*;
50-
import static org.javalite.common.Util.*;
48+
import static org.javalite.common.Util.empty;
49+
import static org.javalite.common.Util.join;
5150

5251
/**
5352
* This class is a super class of all "models" and provides most functionality
@@ -196,7 +195,7 @@ protected void hydrate(Map<String, Object> attributesMap, boolean fireAfterLoad)
196195
}
197196
}
198197
}
199-
if (getIdCompositeKeys() != null){
198+
if (getCompositeKeys() != null){
200199
compositeKeyPersisted = true;
201200
}
202201
if(fireAfterLoad){
@@ -393,8 +392,8 @@ public boolean frozen(){
393392
public boolean delete() {
394393
fireBeforeDelete();
395394
int result;
396-
if (getIdCompositeKeys() != null) {
397-
String[] compositeKeys = getIdCompositeKeys();
395+
if (getCompositeKeys() != null) {
396+
String[] compositeKeys = getCompositeKeys();
398397
StringBuilder query = new StringBuilder();
399398
Object[] values = new Object[compositeKeys.length];
400399
for (int i = 0; i < compositeKeys.length; i++) {
@@ -2197,16 +2196,14 @@ public static <T extends Model> T findById(Object id) {
21972196
}
21982197

21992198
/**
2200-
* Composite PK values only must be ordered like in ({@link IdCompositeKeys}
2201-
* )
2199+
* Composite PK values in exactly the same order as specified in {@link CompositePK}.
22022200
*
2203-
* @param values
2204-
* ordered Composite PK values only!!
2205-
* @return Model or null
2206-
* @see IdCompositeKeys
2201+
* @param values Composite PK values in exactly the same order as specified in {@link CompositePK}.
2202+
* @return instance of a found model, or null if nothing found.
2203+
* @see CompositePK
22072204
*/
22082205
public static <T extends Model> T findByCompositeKeys(Object... values) {
2209-
return ModelDelegate.findByCompositeKeys(Model.<T> modelClass(), values);
2206+
return ModelDelegate.findByCompositeKeys(Model.<T>modelClass(), values);
22102207
}
22112208

22122209
/**
@@ -2615,9 +2612,8 @@ public boolean insert() {
26152612
boolean containsId = (attributes.get(metaModel.getIdName()) != null); // do not use containsKey
26162613
boolean done;
26172614
String query = metaModel.getDialect().insertParametrized(metaModel, columns, containsId);
2618-
if (containsId || getIdCompositeKeys() != null) {
2619-
done = (1 == new DB(metaModel.getDbName()).exec(query, values.toArray()));
2620-
compositeKeyPersisted = done;
2615+
if (containsId || getCompositeKeys() != null) {
2616+
compositeKeyPersisted = done = (1 == new DB(metaModel.getDbName()).exec(query, values.toArray()));
26212617
} else {
26222618
Object id = new DB(metaModel.getDbName()).execInsert(query, metaModel.getIdName(), values.toArray());
26232619
attributes.put(metaModel.getIdName(), id);
@@ -2686,8 +2682,8 @@ private boolean update() {
26862682
if(values.isEmpty())
26872683
return false;
26882684

2689-
if (getIdCompositeKeys() != null) {
2690-
String[] compositeKeys = getIdCompositeKeys();
2685+
if (getCompositeKeys() != null) {
2686+
String[] compositeKeys = getCompositeKeys();
26912687
for (int i = 0; i < compositeKeys.length; i++) {
26922688
query.append(i == 0 ? " WHERE " : " AND ").append(compositeKeys[i]).append(" = ?");
26932689
values.add(get(compositeKeys[i]));
@@ -2730,20 +2726,40 @@ private static <T extends Model> Class<T> modelClass() {
27302726
throw new InitException("failed to determine Model class name, are you sure models have been instrumented?");
27312727
}
27322728

2729+
/**
2730+
* Returns name of corresponding table.
2731+
*
2732+
* @return name of corresponding table.
2733+
*/
27332734
public static String getTableName() {
27342735
return ModelDelegate.tableNameOf(modelClass());
27352736
}
27362737

2738+
/**
2739+
* Value of ID.
2740+
*
2741+
* @return of ID.
2742+
*/
27372743
public Object getId() {
27382744
return get(getIdName());
27392745
}
27402746

2747+
/**
2748+
* Name of ID column.
2749+
*
2750+
* @return Name of ID column.
2751+
*/
27412752
public String getIdName() {
27422753
return getMetaModelLocal().getIdName();
27432754
}
27442755

2745-
public String[] getIdCompositeKeys() {
2746-
return getMetaModelLocal().getIdCompositeKeys();
2756+
/**
2757+
* Provides a list of composite keys as specified in {@link CompositePK}.
2758+
*
2759+
* @return a list of composite keys as specified in {@link CompositePK}.
2760+
*/
2761+
public String[] getCompositeKeys() {
2762+
return getMetaModelLocal().getCompositeKeys();
27472763
}
27482764

27492765
protected void setChildren(Class childClass, List<Model> children) {

activejdbc/src/main/java/org/javalite/activejdbc/ModelDelegate.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ public static <T extends Model> T findById(Class<T> clazz, Object id) {
200200
public static <T extends Model> T findByCompositeKeys(Class<T> clazz, Object...values) {
201201
if (values == null || values.length == 0) { return null; }
202202
MetaModel metaModel = metaModelOf(clazz);
203-
String[] compositeKeys = metaModel.getIdCompositeKeys();
203+
String[] compositeKeys = metaModel.getCompositeKeys();
204204
if (compositeKeys == null || compositeKeys.length != values.length){
205205
return null;
206206
}

activejdbc/src/main/java/org/javalite/activejdbc/annotations/IdCompositeKeys.java renamed to activejdbc/src/main/java/org/javalite/activejdbc/annotations/CompositePK.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111

1212
@Retention(RetentionPolicy.RUNTIME)
1313
@Target(ElementType.TYPE)
14-
public @interface IdCompositeKeys {
15-
public String[] value();
14+
public @interface CompositePK {
15+
String[] value();
1616
}

activejdbc/src/main/java/org/javalite/activejdbc/dialects/DefaultDialect.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,15 +269,15 @@ public String update(MetaModel metaModel, Map<String, Object> attributes) {
269269
break;
270270
}
271271
}
272-
if (metaModel.getIdCompositeKeys() == null){
272+
273+
if (metaModel.getCompositeKeys() == null){
273274
query.append(" WHERE ").append(idName).append(" = ").append(attributes.get(idName));
274275
} else {
275-
String[] compositeKeys = metaModel.getIdCompositeKeys();
276+
String[] compositeKeys = metaModel.getCompositeKeys();
276277
for (int i = 0; i < compositeKeys.length; i++) {
277278
query.append(i == 0 ? " WHERE " : " AND ").append(compositeKeys[i]).append(" = ");
278279
appendValue(query, attributes.get(compositeKeys[i]));
279280
}
280-
281281
}
282282
return query.toString();
283283
}

activejdbc/src/test/java/org/javalite/activejdbc/CompositePkTest.java

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import java.util.NoSuchElementException;
44

55
import org.javalite.activejdbc.test.ActiveJDBCTest;
6-
import org.javalite.activejdbc.test_models.Composites;
6+
import org.javalite.activejdbc.test_models.Developer;
77
import org.junit.Test;
88

99
/**
@@ -14,82 +14,82 @@ public class CompositePkTest extends ActiveJDBCTest {
1414
@Test
1515
public void shouldInsertAndUpdate(){
1616

17-
Composites comp1 = new Composites();
17+
Developer comp1 = new Developer();
1818
comp1.set("first_name", "John", "last_name", "Smith", "email", "smithy@spam.org", "address", "bla");
1919
comp1.insert();
2020

21-
Composites comp2 = new Composites();
21+
Developer comp2 = new Developer();
2222
comp2.set("first_name", "John", "last_name", "Smith2", "email", "smithy@spam.org", "address", "blabla");
2323
comp2.insert();
2424

25-
the(Composites.count()).shouldBeEqual(2);
26-
the(Composites.findByCompositeKeys("John", "Smith", "smithy@spam.org").get("address")).shouldBeEqual("bla");
27-
the(Composites.findByCompositeKeys("John", "Smith2", "smithy@spam.org").get("address")).shouldBeEqual("blabla");
25+
the(Developer.count()).shouldBeEqual(2);
26+
the(Developer.findByCompositeKeys("John", "Smith", "smithy@spam.org").get("address")).shouldBeEqual("bla");
27+
the(Developer.findByCompositeKeys("John", "Smith2", "smithy@spam.org").get("address")).shouldBeEqual("blabla");
2828

29-
Composites comp3 = Composites.findByCompositeKeys("John", "Smith2", "smithy@spam.org");
29+
Developer comp3 = Developer.findByCompositeKeys("John", "Smith2", "smithy@spam.org");
3030
comp3.set("address", "blablabla");
3131
comp3.saveIt();
32-
the(Composites.findByCompositeKeys("John", "Smith2", "smithy@spam.org").get("address")).shouldBeEqual("blablabla");
32+
the(Developer.findByCompositeKeys("John", "Smith2", "smithy@spam.org").get("address")).shouldBeEqual("blablabla");
3333

3434
}
3535

3636
@Test(expected = NoSuchElementException.class)
3737
public void shouldGenerateNoSuchElementFromBlankUpdate() {
38-
Composites c = new Composites();
38+
Developer c = new Developer();
3939
c.toUpdate();
4040
}
4141

4242
@Test
4343
public void shouldGenerateValidSQL() {
44-
Composites comp = new Composites();
45-
comp.set("first_name", "Johnny", "last_name", "Cash", "email", "j.cash@spam.org", "address", "inserted");
46-
the(Base.exec(comp.toInsert())).shouldBeEqual(1);
47-
comp.set("address", "updated");
48-
the(Base.exec(comp.toUpdate())).shouldBeEqual(1);
44+
Developer dev = new Developer();
45+
dev.set("first_name", "Johnny", "last_name", "Cash", "email", "j.cash@spam.org", "address", "inserted");
46+
the(Base.exec(dev.toInsert())).shouldBeEqual(1);
47+
dev.set("address", "updated");
48+
the(Base.exec(dev.toUpdate())).shouldBeEqual(1);
4949

50-
comp = Composites.findByCompositeKeys("Johnny", "Cash", "j.cash@spam.org");
50+
dev = Developer.findByCompositeKeys("Johnny", "Cash", "j.cash@spam.org");
5151

52-
the(comp.get("first_name")).shouldBeEqual("Johnny");
53-
the(comp.get("last_name")).shouldBeEqual("Cash");
54-
the(comp.get("email")).shouldBeEqual("j.cash@spam.org");
55-
the(comp.get("address")).shouldBeEqual("updated");
52+
the(dev.get("first_name")).shouldBeEqual("Johnny");
53+
the(dev.get("last_name")).shouldBeEqual("Cash");
54+
the(dev.get("email")).shouldBeEqual("j.cash@spam.org");
55+
the(dev.get("address")).shouldBeEqual("updated");
5656

57-
comp = Composites.findByCompositeKeys("Johnny", "Casher", "j.cash@spam.org");
58-
the(Composites.findByCompositeKeys("Johnny", "Casher", "j.cash@spam.org")).shouldBeNull();
57+
dev = Developer.findByCompositeKeys("Johnny", "WrongName", "j.cash@spam.org");
58+
the(dev).shouldBeNull();
5959
}
6060

6161
@Test
62-
public void shouldReturnNull() {
63-
Composites.createIt("first_name", "Johnny", "last_name", "Cash", "email", "j.cash@spam.org", "address", "inserted");
64-
the(Composites.findByCompositeKeys("Johnny", "Casher", "j.cash@spam.org")).shouldBeNull();
65-
the(Composites.findByCompositeKeys("John", "Cash", "j.cash@spam.org")).shouldBeNull();
66-
the(Composites.findByCompositeKeys("Johnny", "Cash", "j.cash@spam.com")).shouldBeNull();
62+
public void shouldReturnNullWithWrongPKs() {
63+
Developer.createIt("first_name", "Johnny", "last_name", "Cash", "email", "j.cash@spam.org", "address", "inserted");
64+
the(Developer.findByCompositeKeys("Johnny", "Casher", "j.cash@spam.org")).shouldBeNull();
65+
the(Developer.findByCompositeKeys("John", "Cash", "j.cash@spam.org")).shouldBeNull();
66+
the(Developer.findByCompositeKeys("Johnny", "Cash", "j.cash@spam.com")).shouldBeNull();
6767
}
6868

6969
@Test
70-
public void shouldDeleted() {
71-
Composites.createIt("first_name", "Johnny", "last_name", "Cash", "email", "j.cash@spam.org", "address", "inserted");
72-
Composites comp = Composites.findByCompositeKeys("Johnny", "Cash", "j.cash@spam.org");
70+
public void shouldDeleteByPK() {
71+
Developer.createIt("first_name", "Johnny", "last_name", "Cash", "email", "j.cash@spam.org", "address", "inserted");
72+
Developer comp = Developer.findByCompositeKeys("Johnny", "Cash", "j.cash@spam.org");
7373
the(comp).shouldNotBeNull();
74-
the(Composites.count()).shouldBeEqual(1);
74+
the(Developer.count()).shouldBeEqual(1);
7575
comp.delete();
76-
the(Composites.count()).shouldBeEqual(0);
76+
the(Developer.count()).shouldBeEqual(0);
7777
the(comp.isFrozen()).shouldBeTrue();
7878
}
7979

8080
@Test
8181
public void shouldBeNew() {
8282

83-
Composites comp = new Composites();
83+
Developer comp = new Developer();
8484
comp.set("first_name", "Johnny", "last_name", "Cash", "email", "j.cash@spam.org", "address", "inserted");
8585
the(comp.isNew()).shouldBeTrue();
8686

87-
Composites.createIt("first_name", "Johnny", "last_name", "Cash", "email", "j.cash@spam.org", "address", "inserted");
88-
comp = Composites.findByCompositeKeys("Johnny", "Cash", "j.cash@spam.org");
87+
Developer.createIt("first_name", "Johnny", "last_name", "Cash", "email", "j.cash@spam.org", "address", "inserted");
88+
comp = Developer.findByCompositeKeys("Johnny", "Cash", "j.cash@spam.org");
8989
the(comp).shouldNotBeNull();
9090
the(comp.isNew()).shouldBeFalse();
9191

92-
comp = Composites.createIt("first_name", "Johnny", "last_name", "Cash2", "email", "j.cash@spam.org", "address", "inserted");
92+
comp = Developer.createIt("first_name", "Johnny", "last_name", "Cash2", "email", "j.cash@spam.org", "address", "inserted");
9393
the(comp.isNew()).shouldBeFalse();
9494
}
9595
}

activejdbc/src/test/java/org/javalite/activejdbc/test_models/Composites.java renamed to activejdbc/src/test/java/org/javalite/activejdbc/test_models/Developer.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package org.javalite.activejdbc.test_models;
22

33
import org.javalite.activejdbc.Model;
4-
import org.javalite.activejdbc.annotations.IdCompositeKeys;
4+
import org.javalite.activejdbc.annotations.CompositePK;
55

6-
@IdCompositeKeys({ "first_name", "last_name", "email" })
7-
public class Composites extends Model {
6+
@CompositePK({ "first_name", "last_name", "email" })
7+
public class Developer extends Model {
88
static {
99
validatePresenceOf("first_name", "last_name", "email").message(
1010
"one or more composite PK's missing!!!");

activejdbc/src/test/resources/h2_schema.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,5 +161,5 @@ CREATE TABLE apples (id int(11) NOT NULL PRIMARY KEY, apple_type VARCHAR(56) NOT
161161
DROP TABLE IF EXISTS alarms;
162162
CREATE TABLE alarms (id int(11) NOT NULL auto_increment PRIMARY KEY, alarm_time TIME NOT NULL);
163163

164-
DROP TABLE IF EXISTS composites;
165-
CREATE TABLE composites (first_name VARCHAR(56) NOT NULL, last_name VARCHAR(56) NOT NULL, email VARCHAR(56) NOT NULL, address VARCHAR(56), CONSTRAINT composites_uq PRIMARY KEY (first_name, last_name, email));
164+
DROP TABLE IF EXISTS developers;
165+
CREATE TABLE developers (first_name VARCHAR(56) NOT NULL, last_name VARCHAR(56) NOT NULL, email VARCHAR(56) NOT NULL, address VARCHAR(56), CONSTRAINT developers_uq PRIMARY KEY (first_name, last_name, email));

activejdbc/src/test/resources/mssql_schema.sql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -330,9 +330,9 @@ END
330330
CREATE TABLE alarms (id INT IDENTITY PRIMARY KEY, alarm_time TIME NOT NULL);
331331

332332

333-
IF object_id('dbo.composites') IS NOT NULL
333+
IF object_id('dbo.developers') IS NOT NULL
334334
BEGIN
335-
DROP TABLE [dbo].[composites]
335+
DROP TABLE [dbo].[developers]
336336
END
337-
CREATE TABLE composites (first_name VARCHAR(56) NOT NULL, last_name VARCHAR(56) NOT NULL, email VARCHAR(56) NOT NULL, address VARCHAR(56), CONSTRAINT composites_uq UNIQUE (first_name, last_name, email));
337+
CREATE TABLE developers (first_name VARCHAR(56) NOT NULL, last_name VARCHAR(56) NOT NULL, email VARCHAR(56) NOT NULL, address VARCHAR(56), CONSTRAINT developers_uq UNIQUE (first_name, last_name, email));
338338

0 commit comments

Comments
 (0)