Permalink
Browse files

Support for transactions and work units in arbitrary SQL backends (si…

…milar to Spring JDBC template)
  • Loading branch information...
1 parent 37bb91a commit c4f798c2e7bdf6c01fbfe593ad20f262071fa649 @dhanji committed Nov 28, 2012
View
@@ -22,11 +22,7 @@ This following misspelling results in a template compile error:
Total errors: 1
-### Next steps ###
-
-Get started
-5-minute tutorial
-Building RESTful web services
+[Next steps](http://sitebricks.org)
* * *
View
@@ -116,7 +116,7 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
- <version>r09</version>
+ <version>13.0</version>
</dependency>
<dependency>
@@ -11,6 +11,22 @@
<artifactId>sitebricks-persist-sql</artifactId>
<name>Sitebricks :: Persistence Module (SQL)</name>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>com.jolbox</groupId>
+ <artifactId>bonecp</artifactId>
+ <version>0.8.0-alpha1</version>
+ <exclusions>
+ <exclusion>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
<dependency>
<groupId>org.testng</groupId>
@@ -34,11 +50,24 @@
<groupId>com.jolbox</groupId>
<artifactId>bonecp</artifactId>
<version>0.8.0-alpha1</version>
+ <exclusions>
+ <exclusion>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </exclusion>
+ </exclusions>
</dependency>
+ <dependency>
+ <groupId>org.hsqldb</groupId>
+ <artifactId>hsqldb</artifactId>
+ <version>2.2.9</version>
+ <scope>test</scope>
+ </dependency>
+
</dependencies>
<build>
- <finalName>sitebricks-persist-disk</finalName>
+ <finalName>sitebricks-persist-sql</finalName>
<resources>
<resource>
<directory>src/test/resources</directory>
@@ -1,5 +1,7 @@
package com.google.sitebricks.persist.sql;
+import com.google.common.collect.ImmutableMap;
+
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
@@ -28,13 +30,20 @@ public Sql(Connection connection) {
this.connection = connection;
}
+ public void execute(String sql) {
+ query(sql);
+ }
+
public void execute(String sql, Map<String, Object> params) {
query(sql, params);
}
+ public ResultSet query(String sql) {
+ return query(sql, ImmutableMap.<String, Object>of());
+ }
+
public ResultSet query(String sql, Map<String, Object> params) {
try {
-
Matcher matcher = Sql.NAMED_ARG_PATTERN.matcher(sql);
Map<Integer, Object> positionalParams = toPositionalMap(params, matcher);
@@ -75,6 +84,18 @@ public ResultSet query(String sql, Map<String, Object> params) {
return positionalParams;
}
+ public boolean tableExists(String name) {
+ try {
+ return connection.getMetaData().getTables(null, null, name.toUpperCase(), null).next();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public List<Map<String, Object>> list(String sql) {
+ return list(sql, ImmutableMap.<String, Object>of());
+ }
+
public List<Map<String, Object>> list(String sql, Map<String, Object> params) {
ResultSet resultSet = query(sql, params);
@@ -85,8 +106,8 @@ public ResultSet query(String sql, Map<String, Object> params) {
while (resultSet.next()) {
Map<String, Object> row = new HashMap<String, Object>();
- for (int i = 0; i < count; i++) {
- String column = metaData.getColumnName(i);
+ for (int i = 1; i <= count; i++) {
+ String column = metaData.getColumnName(i).toLowerCase();
row.put(column, resultSet.getObject(column));
}
@@ -0,0 +1,227 @@
+package com.google.sitebricks.persist.sql;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.sitebricks.persist.EntityStore;
+import com.google.sitebricks.persist.PersistAopModule;
+import com.google.sitebricks.persist.Persister;
+import com.google.sitebricks.persist.Transactional;
+import com.google.sitebricks.persist.Work;
+import com.jolbox.bonecp.BoneCPConfig;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertTrue;
+
+/**
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+public class SqlStoreIntegrationTest {
+ public static final String A_NAME = "Jason Van Zyl";
+ private static final String TEST_DB_FILE = "target/test_sql_db";
+ private static final String ANOTHER_NAME = "Jason";
+
+ public static class SqlSaver {
+ @Inject
+ Provider<Sql> sql;
+
+ @Work
+ public void make() {
+ sql.get().execute("insert into my_table (id, name) values (1, @name)",
+ ImmutableMap.<String, Object>of("name", A_NAME));
+ }
+
+ @Work
+ public String find() {
+ List<Map<String,Object>> list = sql.get().list("select * from my_table");
+ assertFalse(list.isEmpty());
+ return list.iterator().next().get("name").toString();
+ }
+ }
+
+ public static class SqlTransactionalSaver {
+ @Inject
+ Provider<Sql> sql;
+
+ @Work @Transactional
+ public void make() {
+ sql.get().execute("insert into my_table (id, name) values (1, @name)",
+ ImmutableMap.<String, Object>of("name", ANOTHER_NAME));
+ }
+
+ @Work @Transactional
+ public String find() {
+ return sql.get().list("select * from my_table").iterator().next().get("name").toString();
+ }
+ }
+
+ private static final AtomicInteger dbCount = new AtomicInteger(1);
+ private BoneCPConfig config;
+
+ @BeforeMethod
+ public final void pre() {
+ String database = "db" + dbCount.incrementAndGet();
+
+ config = new BoneCPConfig();
+ config.setJdbcUrl("jdbc:hsqldb:mem:" + database + ";sql.syntax_mys=true");
+ config.setUser("sa");
+ config.setPassword("");
+ }
+
+ private static void createTable(Injector injector) {
+ Persister persister = injector.getInstance(Persister.class);
+ persister.call(new Persister.InWork() {
+ @Override
+ public Object perform(EntityStore es) throws Throwable {
+ ((Sql) es.delegate()).execute("create table my_table (id integer, name text not null)");
+ return null;
+ }
+ });
+
+ final AtomicBoolean tableExists = new AtomicBoolean();
+ persister.call(new Persister.InWork() {
+ @Override
+ public Object perform(EntityStore es) throws Throwable {
+ tableExists.set(((Sql) es.delegate()).tableExists("my_table"));
+ return null;
+ }
+ });
+
+ assertTrue(tableExists.get());
+ }
+
+ @Test
+ public final void storeAndRetrieve() {
+ SqlModule redisModule = new SqlModule(config);
+ Injector injector = Guice.createInjector(redisModule, new PersistAopModule(redisModule));
+ createTable(injector);
+ SqlSaver saver = injector.getInstance(SqlSaver.class);
+
+ saver.make();
+
+ assertEquals(A_NAME, saver.find());
+ }
+
+
+ @Test
+ public final void storeAndRetrieveTransactional() {
+ SqlModule redisModule = new SqlModule(config);
+ Injector injector = Guice.createInjector(redisModule, new PersistAopModule(redisModule));
+ createTable(injector);
+ SqlTransactionalSaver saver = injector
+ .getInstance(SqlTransactionalSaver.class);
+
+ saver.make();
+
+ assertEquals(ANOTHER_NAME, saver.find());
+ }
+
+ @Test
+ public final void storeAndRetrieveWithoutAop() {
+ Injector injector = Guice.createInjector(new SqlModule(config));
+ createTable(injector);
+ Persister persister = injector
+ .getInstance(Persister.class);
+
+ persister.call(new Persister.InWork() {
+ @Override
+ public Object perform(EntityStore es) throws Throwable {
+ ((Sql)es.delegate()).execute("insert into my_table (id, name) values (1, @name)",
+ ImmutableMap.<String, Object>of("name", A_NAME));
+
+ return null;
+ }
+ });
+
+ Object param = persister.call(new Persister.InWork() {
+ @Override
+ public Object perform(EntityStore es) throws Throwable {
+ return ((Sql)es.delegate()).list("select * from my_table").iterator().next().get("name").toString();
+ }
+ });
+
+ assertEquals(A_NAME, param);
+ }
+
+ @Test
+ public final void storeAndRemoveWithoutAop() {
+ Injector injector = Guice.createInjector(new SqlModule(config));
+ createTable(injector);
+ Persister persister = injector.getInstance(Persister.class);
+
+ persister.call(new Persister.InWork() {
+ @Override
+ public Object perform(EntityStore es) throws Throwable {
+ ((Sql)es.delegate()).execute("insert into my_table (id, name) values (1, @name)",
+ ImmutableMap.<String, Object>of("name", A_NAME));
+
+ return null;
+ }
+ });
+
+ persister.call(new Persister.InWork() {
+ @Override
+ public Object perform(EntityStore es) throws Throwable {
+ ((Sql)es.delegate()).execute("delete from my_table where name = @name and id = @id",
+ ImmutableMap.<String, Object>of(
+ "id", 1,
+ "name", A_NAME
+ ));
+
+ return null;
+ }
+ });
+
+ Object param = persister.call(new Persister.InWork() {
+ @Override
+ public Object perform(EntityStore es) throws Throwable {
+ return ((Sql)es.delegate()).list("select * from my_table");
+ }
+ });
+
+ assertTrue(param instanceof List);
+ assertTrue(((List)param).isEmpty());
+ }
+
+ @Test // No different in Redis to @Work
+ public final void storeAndRetrieveInTransaction() {
+ Injector injector = Guice.createInjector(new SqlModule(config));
+ createTable(injector);
+ final Persister persister = injector
+ .getInstance(Persister.class);
+
+ Object param = persister.call(new Persister.InWork() {
+ @Override
+ public Object perform(EntityStore es) throws Throwable {
+ persister.call(new Persister.InTransaction() {
+ @Override
+ public Object perform(EntityStore es) throws Throwable {
+ ((Sql)es.delegate()).execute("insert into my_table (id, name) values (1, @name)",
+ ImmutableMap.<String, Object>of("name", ANOTHER_NAME));
+
+ return null;
+ }
+ });
+
+ return persister.call(new Persister.InTransaction() {
+ @Override
+ public Object perform(EntityStore es) throws Throwable {
+ return ((Sql)es.delegate()).list("select * from my_table").iterator().next().get("name").toString();
+ }
+ });
+ }
+ });
+
+ assertEquals(ANOTHER_NAME, param);
+ }
+}
@@ -15,6 +15,10 @@
<artifactId>guice</artifactId>
</dependency>
<dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
<groupId>cglib</groupId>
<artifactId>cglib-full</artifactId>
</dependency>

0 comments on commit c4f798c

Please sign in to comment.