Skip to content

Commit 9ecb1df

Browse files
#58 - Add sample for usage of Serverside Java Script.
Use a serverside script to simulate putIfAbsent as contracted by the Map interface. The op will be atomic since mongoDB will put a lock on db until the script is finished.
1 parent 2c97493 commit 9ecb1df

File tree

6 files changed

+203
-5
lines changed

6 files changed

+203
-5
lines changed

mongodb/example/src/main/java/example/springdata/mongodb/advanced/AdvancedRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
*
2828
* @author Christoph Strobl
2929
*/
30-
public interface AdvancedRepository extends CustomerRepository {
30+
public interface AdvancedRepository extends CustomerRepository, AdvancedRepositoryCustom {
3131

3232
String META_COMMENT = "s2gx-2014-rocks!";
3333

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.mongodb.advanced;
17+
18+
import java.util.Map;
19+
20+
import example.springdata.mongodb.customer.Customer;
21+
22+
/**
23+
* @author Christoph Strobl
24+
*/
25+
public interface AdvancedRepositoryCustom {
26+
27+
/**
28+
* If the specified customer is not already persisted in MongoDB (or is mapped to {@code null}) associates it with the
29+
* given value and returns {@code null}, else returns the current value.
30+
*
31+
* @see Map#putIfAbsent(Object, Object)
32+
* @param customer
33+
* @return {@literal null} when inserted otherwise existing {@link Customer}
34+
*/
35+
Customer putIfAbsent(Customer customer);
36+
37+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.mongodb.advanced;
17+
18+
import org.springframework.beans.factory.annotation.Autowired;
19+
import org.springframework.data.mongodb.core.MongoOperations;
20+
import org.springframework.data.mongodb.core.script.ExecutableMongoScript;
21+
22+
import com.mongodb.BasicDBObject;
23+
import com.mongodb.DBObject;
24+
25+
import example.springdata.mongodb.customer.Customer;
26+
27+
/**
28+
* @author Christoph Strobl
29+
*/
30+
public class AdvancedRepositoryImpl implements AdvancedRepositoryCustom {
31+
32+
@Autowired MongoOperations mongoOps;
33+
34+
/*
35+
* (non-Javadoc)
36+
* @see example.springdata.mongodb.advanced.AdvancedRepositoryCustom#putIfAbsent(example.springdata.mongodb.customer.Customer)
37+
*/
38+
@Override
39+
public Customer putIfAbsent(Customer customer) {
40+
41+
if (!mongoOps.collectionExists(Customer.class)) {
42+
mongoOps.createCollection(Customer.class);
43+
}
44+
45+
Object result = mongoOps.scriptOps().execute(createScript(customer));
46+
if (result == null) {
47+
return null;
48+
}
49+
50+
return mongoOps.getConverter().read(Customer.class, (DBObject) result);
51+
}
52+
53+
private ExecutableMongoScript createScript(Object entity) {
54+
55+
String collectionName = mongoOps.getCollectionName(Customer.class);
56+
Object id = mongoOps.getConverter().getMappingContext().getPersistentEntity(Customer.class)
57+
.getIdentifierAccessor(entity).getIdentifier();
58+
59+
DBObject dbo = new BasicDBObject();
60+
mongoOps.getConverter().write(entity, dbo);
61+
62+
String scriptString = String
63+
.format(
64+
"object = db.%1$s.findOne('{\"_id\": \"%2$s\"}'); if(object == null) { db.%1s.insert(%3$s); return null; } else { return object; }",
65+
collectionName, id, dbo);
66+
67+
return new ExecutableMongoScript(scriptString);
68+
}
69+
}

mongodb/example/src/main/java/example/springdata/mongodb/customer/Customer.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import lombok.Data;
1919

20-
import org.bson.types.ObjectId;
2120
import org.springframework.data.mongodb.core.mapping.Document;
2221
import org.springframework.util.Assert;
2322

@@ -30,7 +29,7 @@
3029
@Document
3130
public class Customer {
3231

33-
private ObjectId id;
32+
private String id;
3433
private String firstname, lastname;
3534

3635
private Address address;

mongodb/example/src/main/java/example/springdata/mongodb/customer/CustomerRepository.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import java.util.List;
1919

20-
import org.bson.types.ObjectId;
2120
import org.springframework.data.domain.Sort;
2221
import org.springframework.data.geo.Distance;
2322
import org.springframework.data.geo.GeoResults;
@@ -29,7 +28,7 @@
2928
*
3029
* @author Oliver Gierke
3130
*/
32-
public interface CustomerRepository extends CrudRepository<Customer, ObjectId> {
31+
public interface CustomerRepository extends CrudRepository<Customer, String> {
3332

3433
/**
3534
* Derived query using dynamic sort information.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.mongodb.advanced;
17+
18+
import static org.hamcrest.core.Is.*;
19+
import static org.hamcrest.core.IsNull.*;
20+
import static org.junit.Assert.*;
21+
22+
import java.util.Map;
23+
24+
import org.junit.Before;
25+
import org.junit.ClassRule;
26+
import org.junit.Test;
27+
import org.junit.runner.RunWith;
28+
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.boot.test.SpringApplicationConfiguration;
30+
import org.springframework.data.mongodb.core.MongoOperations;
31+
import org.springframework.data.mongodb.core.script.ExecutableMongoScript;
32+
import org.springframework.data.mongodb.core.script.NamedMongoScript;
33+
import org.springframework.data.util.Version;
34+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
35+
36+
import com.mongodb.BasicDBObject;
37+
38+
import example.springdata.mongodb.customer.Customer;
39+
import example.springdata.mongodb.util.RequiresMongoDB;
40+
41+
/**
42+
* @author Christoph Strobl
43+
*/
44+
@RunWith(SpringJUnit4ClassRunner.class)
45+
@SpringApplicationConfiguration(classes = ApplicationConfiguration.class)
46+
public class ServersideScriptTests {
47+
48+
@ClassRule public static RequiresMongoDB mongodbAvailable = RequiresMongoDB.atLeast(new Version(2, 6));
49+
50+
@Autowired AdvancedRepository repository;
51+
@Autowired MongoOperations operations;
52+
53+
@Before
54+
public void setUp() {
55+
56+
// just make sure we remove everything properly
57+
operations.getCollection("system.js").remove(new BasicDBObject());
58+
repository.deleteAll();
59+
}
60+
61+
/**
62+
* Use a script execution to create an atomic put-if-absent operation that fulfills the contract of
63+
* {@link Map#putIfAbsent(Object, Object)}
64+
*/
65+
@Test
66+
public void putIfAbsent() {
67+
68+
Customer ned = new Customer("Ned", "Stark");
69+
ned.setId("ned-stark");
70+
71+
// #1: on first insert null has to be returned
72+
assertThat(repository.putIfAbsent(ned), nullValue());
73+
74+
// #2: change the firstname and put again, we expect to the existing customer without the change
75+
ned.setFirstname("Eddard");
76+
assertThat(repository.putIfAbsent(ned).getFirstname(), is("Ned"));
77+
78+
// #3: make sure the entity has not been altered by #2
79+
assertThat(repository.findOne(ned.getId()).getFirstname(), is("Ned"));
80+
}
81+
82+
/**
83+
* Store and call an arbitary JavaScript function (in this case a simple echo script) via its name.
84+
*/
85+
@Test
86+
public void simpleScriptExecution() {
87+
88+
operations.scriptOps().register(
89+
new NamedMongoScript("echoScript", new ExecutableMongoScript("function(x) { return x; }")));
90+
91+
Object o = operations.scriptOps().call("echoScript", "Hello echo...!");
92+
assertThat(o, is((Object) "Hello echo...!"));
93+
}
94+
}

0 commit comments

Comments
 (0)