Skip to content

Commit 6783250

Browse files
author
Igor Polevoy
committed
Merge pull request #402 from comtel2000/composite-pk
Limited Composite PK support
2 parents 9bda4cd + 0329492 commit 6783250

File tree

15 files changed

+270
-26
lines changed

15 files changed

+270
-26
lines changed

activejdbc/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@
201201
<artifactId>maven-surefire-plugin</artifactId>
202202
<version>2.18</version>
203203
<configuration>
204+
<!-- fails with DE Locale as system default -->
205+
<argLine>-Duser.country=US -Duser.language=en</argLine>
204206
<trimStackTrace>false</trimStackTrace>
205207
<useFile>false</useFile>
206208
<includes>

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +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;
3839
private final String tableName, dbType, dbName;
3940
private final Class<? extends Model> modelClass;
4041
private final boolean cached;
@@ -45,6 +46,7 @@ public class MetaModel implements Serializable {
4546
protected MetaModel(String dbName, Class<? extends Model> modelClass, String dbType) {
4647
this.modelClass = modelClass;
4748
this.idName = findIdName(modelClass);
49+
this.idCompositeKeys = findIdCompositeKeys(modelClass);
4850
this.tableName = findTableName(modelClass);
4951
this.dbType = dbType;
5052
this.cached = isCached(modelClass);
@@ -62,6 +64,11 @@ private String findIdName(Class<? extends Model> modelClass) {
6264
return idNameAnnotation == null ? "id" : idNameAnnotation.value();
6365
}
6466

67+
private String[] findIdCompositeKeys(Class<? extends Model> modelClass) {
68+
IdCompositeKeys idCompositeKeysAnnotation = modelClass.getAnnotation(IdCompositeKeys.class);
69+
return idCompositeKeysAnnotation == null ? null : idCompositeKeysAnnotation.value();
70+
}
71+
6572
private String findTableName(Class<? extends Model> modelClass) {
6673
Table tableAnnotation = modelClass.getAnnotation(Table.class);
6774
return tableAnnotation == null ? tableize(modelClass.getSimpleName()) : tableAnnotation.value();
@@ -195,6 +202,15 @@ public String getIdName() {
195202
return idName;
196203
}
197204

205+
/**
206+
* Returns optional composite primary key class
207+
*
208+
* @return composite primary key class
209+
*/
210+
public String[] getIdCompositeKeys() {
211+
return idCompositeKeys;
212+
}
213+
198214
/**
199215
* Returns association of this table with the target table. Will return null if there is no association.
200216
*

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

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.javalite.activejdbc;
1717

18+
import org.javalite.activejdbc.annotations.IdCompositeKeys;
1819
import org.javalite.activejdbc.associations.*;
1920
import org.javalite.activejdbc.cache.QueryCache;
2021
import org.javalite.activejdbc.conversion.Converter;
@@ -31,13 +32,15 @@
3132
import org.w3c.dom.NodeList;
3233

3334
import javax.xml.parsers.DocumentBuilderFactory;
35+
3436
import java.io.*;
3537
import java.math.BigDecimal;
3638
import java.sql.Clob;
3739
import java.sql.Time;
3840
import java.sql.Timestamp;
3941
import java.text.DateFormat;
4042
import java.util.*;
43+
4144
import org.javalite.activejdbc.conversion.BlankToNullConverter;
4245
import org.javalite.activejdbc.conversion.ZeroToNullConverter;
4346
import org.javalite.activejdbc.dialects.Dialect;
@@ -65,7 +68,7 @@ public abstract class Model extends CallbackSupport implements Externalizable {
6568
private final Map<Class, Model> cachedParents = new HashMap<Class, Model>();
6669
private final Map<Class, List<Model>> cachedChildren = new HashMap<Class, List<Model>>();
6770
private boolean manageTime = true;
68-
71+
private boolean compositeKeyPersisted = false;
6972
private Errors errors = new Errors();
7073

7174
protected Model() {
@@ -193,7 +196,9 @@ protected void hydrate(Map<String, Object> attributesMap, boolean fireAfterLoad)
193196
}
194197
}
195198
}
196-
199+
if (getIdCompositeKeys() != null){
200+
compositeKeyPersisted = true;
201+
}
197202
if(fireAfterLoad){
198203
fireAfterLoad();
199204
}
@@ -365,7 +370,7 @@ public static List<Association> associations() {
365370
* @return true if this is a new instance, not saved yet to DB, false otherwise
366371
*/
367372
public boolean isNew(){
368-
return getId() == null;
373+
return getId() == null && !compositeKeyPersisted;
369374
}
370375

371376

@@ -387,23 +392,32 @@ public boolean frozen(){
387392
*/
388393
public boolean delete() {
389394
fireBeforeDelete();
390-
boolean result;
391-
if( 1 == new DB(getMetaModelLocal().getDbName()).exec("DELETE FROM " + getMetaModelLocal().getTableName()
392-
+ " WHERE " + getIdName() + "= ?", getId())) {
393-
394-
frozen = true;
395-
if(getMetaModelLocal().cached()){
396-
QueryCache.instance().purgeTableCache(getMetaModelLocal().getTableName());
397-
}
398-
ModelDelegate.purgeEdges(getMetaModelLocal());
399-
result = true;
400-
}
401-
else{
402-
result = false;
403-
}
404-
fireAfterDelete();
405-
return result;
406-
}
395+
int result;
396+
if (getIdCompositeKeys() != null) {
397+
String[] compositeKeys = getIdCompositeKeys();
398+
StringBuilder query = new StringBuilder();
399+
Object[] values = new Object[compositeKeys.length];
400+
for (int i = 0; i < compositeKeys.length; i++) {
401+
query.append(i == 0 ? "DELETE FROM " + getMetaModelLocal().getTableName() + " WHERE " : " AND ").append(compositeKeys[i]).append(" = ?");
402+
values[i] = get(compositeKeys[i]);
403+
}
404+
result = new DB(getMetaModelLocal().getDbName()).exec(query.toString(), values);
405+
} else {
406+
result = new DB(getMetaModelLocal().getDbName()).exec("DELETE FROM " + getMetaModelLocal().getTableName()
407+
+ " WHERE " + getIdName() + "= ?", getId());
408+
}
409+
if (1 == result) {
410+
frozen = true;
411+
if (getMetaModelLocal().cached()) {
412+
QueryCache.instance().purgeTableCache(getMetaModelLocal().getTableName());
413+
}
414+
ModelDelegate.purgeEdges(getMetaModelLocal());
415+
fireAfterDelete();
416+
return true;
417+
}
418+
fireAfterDelete();
419+
return false;
420+
}
407421

408422

409423
/**
@@ -2182,6 +2196,19 @@ public static <T extends Model> T findById(Object id) {
21822196
return ModelDelegate.findById(Model.<T>modelClass(), id);
21832197
}
21842198

2199+
/**
2200+
* Composite PK values only must be ordered like in ({@link IdCompositeKeys}
2201+
* )
2202+
*
2203+
* @param values
2204+
* ordered Composite PK values only!!
2205+
* @return Model or null
2206+
* @see IdCompositeKeys
2207+
*/
2208+
public static <T extends Model> T findByCompositeKeys(Object... values) {
2209+
return ModelDelegate.findByCompositeKeys(Model.<T> modelClass(), values);
2210+
}
2211+
21852212
/**
21862213
* Finder method for DB queries based on table represented by this model. Usually the SQL starts with:
21872214
*
@@ -2480,6 +2507,7 @@ public void reset() {
24802507
*/
24812508
public void thaw(){
24822509
attributes.put(getIdName(), null);
2510+
compositeKeyPersisted = false;
24832511
dirtyAttributeNames.addAll(attributes.keySet());
24842512
frozen = false;
24852513
}
@@ -2523,7 +2551,7 @@ public boolean save() {
25232551
}
25242552

25252553
boolean result;
2526-
if (getId() == null) {
2554+
if (getId() == null && !compositeKeyPersisted) {
25272555
result = insert();
25282556
} else {
25292557
result = update();
@@ -2587,8 +2615,9 @@ public boolean insert() {
25872615
boolean containsId = (attributes.get(metaModel.getIdName()) != null); // do not use containsKey
25882616
boolean done;
25892617
String query = metaModel.getDialect().insertParametrized(metaModel, columns, containsId);
2590-
if (containsId) {
2618+
if (containsId || getIdCompositeKeys() != null) {
25912619
done = (1 == new DB(metaModel.getDbName()).exec(query, values.toArray()));
2620+
compositeKeyPersisted = done;
25922621
} else {
25932622
Object id = new DB(metaModel.getDbName()).execInsert(query, metaModel.getIdName(), values.toArray());
25942623
attributes.put(metaModel.getIdName(), id);
@@ -2656,8 +2685,17 @@ private boolean update() {
26562685
}
26572686
if(values.isEmpty())
26582687
return false;
2659-
query.append(" WHERE ").append(metaModel.getIdName()).append(" = ?");
2660-
values.add(getId());
2688+
2689+
if (getIdCompositeKeys() != null) {
2690+
String[] compositeKeys = getIdCompositeKeys();
2691+
for (int i = 0; i < compositeKeys.length; i++) {
2692+
query.append(i == 0 ? " WHERE " : " AND ").append(compositeKeys[i]).append(" = ?");
2693+
values.add(get(compositeKeys[i]));
2694+
}
2695+
} else {
2696+
query.append(" WHERE ").append(metaModel.getIdName()).append(" = ?");
2697+
values.add(getId());
2698+
}
26612699
if (metaModel.isVersioned()) {
26622700
query.append(" AND ").append(getMetaModelLocal().getVersionColumn()).append(" = ?");
26632701
values.add(get(getMetaModelLocal().getVersionColumn()));
@@ -2704,6 +2742,10 @@ public String getIdName() {
27042742
return getMetaModelLocal().getIdName();
27052743
}
27062744

2745+
public String[] getIdCompositeKeys() {
2746+
return getMetaModelLocal().getIdCompositeKeys();
2747+
}
2748+
27072749
protected void setChildren(Class childClass, List<Model> children) {
27082750
cachedChildren.put(childClass, children);
27092751
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,22 @@ public static <T extends Model> T findById(Class<T> clazz, Object id) {
197197
return list.isEmpty() ? null : list.get(0);
198198
}
199199

200+
public static <T extends Model> T findByCompositeKeys(Class<T> clazz, Object...values) {
201+
if (values == null || values.length == 0) { return null; }
202+
MetaModel metaModel = metaModelOf(clazz);
203+
String[] compositeKeys = metaModel.getIdCompositeKeys();
204+
if (compositeKeys == null || compositeKeys.length != values.length){
205+
return null;
206+
}
207+
StringBuilder sb = new StringBuilder();
208+
for (int i = 0; i < compositeKeys.length; i++) {
209+
sb.append(i == 0 ? "" : " AND ").append(compositeKeys[i])
210+
.append(" = ?");
211+
}
212+
LazyList<T> list = new LazyList<T>(sb.toString(), metaModel, values).limit(1);
213+
return list.isEmpty() ? null : list.get(0);
214+
}
215+
200216
public static <T extends Model> LazyList<T> findBySql(Class<T> clazz, String fullQuery, Object... params) {
201217
return new LazyList<T>(false, metaModelOf(clazz), fullQuery, params);
202218
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.javalite.activejdbc.annotations;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* @author comtel2000
10+
*/
11+
12+
@Retention(RetentionPolicy.RUNTIME)
13+
@Target(ElementType.TYPE)
14+
public @interface IdCompositeKeys {
15+
public String[] value();
16+
}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,16 @@ public String update(MetaModel metaModel, Map<String, Object> attributes) {
269269
break;
270270
}
271271
}
272-
query.append(" WHERE ").append(idName).append(" = ").append(attributes.get(idName));
272+
if (metaModel.getIdCompositeKeys() == null){
273+
query.append(" WHERE ").append(idName).append(" = ").append(attributes.get(idName));
274+
} else {
275+
String[] compositeKeys = metaModel.getIdCompositeKeys();
276+
for (int i = 0; i < compositeKeys.length; i++) {
277+
query.append(i == 0 ? " WHERE " : " AND ").append(compositeKeys[i]).append(" = ");
278+
appendValue(query, attributes.get(compositeKeys[i]));
279+
}
280+
281+
}
273282
return query.toString();
274283
}
275284
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package org.javalite.activejdbc;
2+
3+
import java.util.NoSuchElementException;
4+
5+
import org.javalite.activejdbc.test.ActiveJDBCTest;
6+
import org.javalite.activejdbc.test_models.Composites;
7+
import org.junit.Test;
8+
9+
/**
10+
* @author comtel2000
11+
*/
12+
public class CompositePkTest extends ActiveJDBCTest {
13+
14+
@Test
15+
public void shouldInsertAndUpdate(){
16+
17+
Composites comp1 = new Composites();
18+
comp1.set("first_name", "John", "last_name", "Smith", "email", "smithy@spam.org", "address", "bla");
19+
comp1.insert();
20+
21+
Composites comp2 = new Composites();
22+
comp2.set("first_name", "John", "last_name", "Smith2", "email", "smithy@spam.org", "address", "blabla");
23+
comp2.insert();
24+
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");
28+
29+
Composites comp3 = Composites.findByCompositeKeys("John", "Smith2", "smithy@spam.org");
30+
comp3.set("address", "blablabla");
31+
comp3.saveIt();
32+
the(Composites.findByCompositeKeys("John", "Smith2", "smithy@spam.org").get("address")).shouldBeEqual("blablabla");
33+
34+
}
35+
36+
@Test(expected = NoSuchElementException.class)
37+
public void shouldGenerateNoSuchElementFromBlankUpdate() {
38+
Composites c = new Composites();
39+
c.toUpdate();
40+
}
41+
42+
@Test
43+
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);
49+
50+
comp = Composites.findByCompositeKeys("Johnny", "Cash", "j.cash@spam.org");
51+
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");
56+
57+
comp = Composites.findByCompositeKeys("Johnny", "Casher", "j.cash@spam.org");
58+
the(Composites.findByCompositeKeys("Johnny", "Casher", "j.cash@spam.org")).shouldBeNull();
59+
}
60+
61+
@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();
67+
}
68+
69+
@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");
73+
the(comp).shouldNotBeNull();
74+
the(Composites.count()).shouldBeEqual(1);
75+
comp.delete();
76+
the(Composites.count()).shouldBeEqual(0);
77+
the(comp.isFrozen()).shouldBeTrue();
78+
}
79+
80+
@Test
81+
public void shouldBeNew() {
82+
83+
Composites comp = new Composites();
84+
comp.set("first_name", "Johnny", "last_name", "Cash", "email", "j.cash@spam.org", "address", "inserted");
85+
the(comp.isNew()).shouldBeTrue();
86+
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");
89+
the(comp).shouldNotBeNull();
90+
the(comp.isNew()).shouldBeFalse();
91+
92+
comp = Composites.createIt("first_name", "Johnny", "last_name", "Cash2", "email", "j.cash@spam.org", "address", "inserted");
93+
the(comp.isNew()).shouldBeFalse();
94+
}
95+
}

0 commit comments

Comments
 (0)