Permalink
Browse files

done mostly w/ java proxy example

  • Loading branch information...
1 parent 98ef073 commit c32264c2b8d93d14ac2634c82b1883f3e3642416 Jakub Holy committed Apr 8, 2011
View
@@ -1 +1,3 @@
target
+testData/testDB
+derby.log
View
@@ -26,6 +26,20 @@
<type>jar</type>
<scope>runtime</scope>
</dependency>
+ <dependency>
+ <groupId>net.jakubholy.testing</groupId>
+ <artifactId>dbunit-embeddedderby-parenttest</artifactId>
+ <version>1.2.0</version>
+ <type>jar</type>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>1.5.6</version>
+ <type>jar</type>
+ <scope>runtime</scope>
+ </dependency>
</dependencies>
<profiles>
@@ -37,7 +37,7 @@ public Object interceptAndLog(ProceedingJoinPoint invocation) throws Throwable {
try {
return invocation.proceed();
} catch (Exception e) {
- Logger.getLogger("aop").warning("LoggingAspect informs you that the method " +
+ Logger.getLogger("AspectJ").warning("LoggingAspect informs you that the method " +
invocation.getSignature().getName() + " failed for the input '" +
invocation.getArgs()[0] + "'. Original exception: " + e);
return null; // normally we would just rethrow it...
@@ -4,26 +4,26 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
+import java.sql.BatchUpdateException;
import java.sql.PreparedStatement;
import java.util.LinkedList;
import java.util.List;
+import java.util.logging.Logger;
/**
* Remember values passed into a sql statement via setString etc. for later logging.
*
* Source: http://theholyjava.wordpress.com/2009/05/23/a-logging-wrapper-around-prepareds/
+ * (slightly adjusted)
*/
class LoggingStatementDecorator implements InvocationHandler {
- /** File's Subversion info (version etc.). */
- public static final String SVN_ID = "$id$";
-
private List<List<Object>> batch = new LinkedList<List<Object>>();
private List<Object> currentRow = new LinkedList<Object>();
private PreparedStatement target;
- private boolean failed = false;
+ private int successfulBatchCounter = 0;
- public LoggingStatementDecorator(PreparedStatement target) {
+ private LoggingStatementDecorator(PreparedStatement target) {
if (target == null) throw new IllegalArgumentException("'target' can't be null.");
this.target = target;
}
@@ -32,33 +32,46 @@ public LoggingStatementDecorator(PreparedStatement target) {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
- final Object result;
-
try {
- result = method.invoke(target, args);
- failed = false;
+ Object result = method.invoke(target, args);
+ updateLog(method, args);
+ return result;
} catch (InvocationTargetException e) {
- failed = true;
- throw e.getTargetException();
- } catch (Exception e) {
- failed = true;
- throw e;
+ Throwable cause = e.getTargetException();
+ if (cause instanceof BatchUpdateException) {
+ int failedBatchNr = successfulBatchCounter + 1;
+ Logger.getLogger("JavaProxy").warning(
+ "Batch update failed for batch# " + failedBatchNr +
+ " (counting from 1) with values: [" +
+ getValuesAsCsv() + "]. Cause: " + cause.getMessage());
+ return null;
+ }
+ throw cause;
}
- if ( method.getName().startsWith("setNull")
+ }
+
+ private void updateLog(Method method, Object[] args) {
+ if ( method.getName().startsWith("setNull")
&& (args.length >=1 && Integer.TYPE == method.getParameterTypes()[0] ) ) {
handleSetSomething((Integer) args[0], null);
} else if ( method.getName().startsWith("set")
&& (args.length >=2 && Integer.TYPE == method.getParameterTypes()[0] ) ) {
handleSetSomething((Integer) args[0], args[1]);
} else if ("addBatch".equals(method.getName())) {
handleAddBatch();
+ } else if ("executeBatch".equals(method.getName())) {
+ handleExecuteBatch();
}
+ }
- return result;
- }
+ private void handleExecuteBatch() {
+ ++successfulBatchCounter;
+ batch.clear();
+
+ }
- private void handleSetSomething(int index, Object value) {
+ private void handleSetSomething(int index, Object value) {
currentRow.add(value);
}
@@ -73,11 +86,6 @@ private void handleAddBatch() {
public PreparedStatement getTarget() { return target; }
- /** Has the last method called on the Statement caused an exception? */
- public boolean isFailed() { return failed; }
-
- public String toString() { return "LoggingHandler[failed="+failed+"]"; }
-
/** Values as comma-separated values. */
public String getValuesAsCsv() {
StringBuilder csv = new StringBuilder();
@@ -94,11 +102,11 @@ public String getValuesAsCsv() {
return csv.toString();
} /* getValuesAsCsv */
- public PreparedStatement createProxy() {
+ public static PreparedStatement createProxy(PreparedStatement target) {
return (PreparedStatement) Proxy.newProxyInstance(
PreparedStatement.class.getClassLoader(),
new Class[] { PreparedStatement.class },
- this);
+ new LoggingStatementDecorator(target));
};
}
@@ -0,0 +1,73 @@
+package iterate.jz2011.codeinjection.javaproxy;
+
+import java.sql.BatchUpdateException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import net.jakubholy.testing.dbunit.embeddeddb.EmbeddedDbTester;
+
+public class Main {
+
+ private static final int BATCH_SIZE = 2;
+
+ private static final String ILLEGAL_VALUE = "Only values of 10 or less characters are allowed";
+
+ private static EmbeddedDbTester testDb = new EmbeddedDbTester();
+
+ private void failingJdbcBatchInsert(Connection connection, Map<Integer, String> data) throws SQLException {
+
+ PreparedStatement rawPrepStmt = connection.prepareStatement("INSERT INTO my_test_schema.my_test_table (id,some_text) VALUES (?,?)");
+ PreparedStatement loggingPrepStmt = LoggingStatementDecorator.createProxy(rawPrepStmt);
+
+ int batchCounter = 0;
+ for (Entry<Integer, String> row : data.entrySet()) {
+ loggingPrepStmt.setInt(1, row.getKey());
+ loggingPrepStmt.setString(2, row.getValue());
+ loggingPrepStmt.addBatch();
+ ++batchCounter;
+
+ if (batchCounter % BATCH_SIZE == 0)
+ loggingPrepStmt.executeBatch();
+ }
+
+ // Execute remaining batches if any
+ if (batchCounter % BATCH_SIZE != 0)
+ loggingPrepStmt.executeBatch();
+
+ }
+
+ /**
+ * Initialize DB, perform updates.
+ */
+ public static void main(String[] args) throws Exception {
+
+ // Create the DB from the DDL
+ try {
+ net.jakubholy.testing.dbunit.embeddeddb.DatabaseCreator.createAndInitializeTestDb();
+ } catch (BatchUpdateException e) {/* perhaps the DB exists already*/}
+
+ // Initialize DB connection, clear existing data from a previous run
+ testDb.onSetup();
+
+ // Prepare & insert data!
+ @SuppressWarnings("serial")
+ Map<Integer, String> data = new HashMap<Integer, String>() {{
+ // Batch 0
+ put(100, "ok value 1");
+ put(200, "ok value 2");
+ // Batch 1
+ put(300, "ok value 3");
+ put(300, ILLEGAL_VALUE);
+ // Batch 2
+ put(400, "ok value 4");
+ }};
+
+ new Main().failingJdbcBatchInsert(testDb.getSqlConnection(), data);
+
+ }
+
+}
@@ -0,0 +1,10 @@
+-- DDL file defining the schemas and tables to create in the test database.
+-- Read and executed by net.jakubholy.testing.dbunit.DatabaseCreator#createDbSchemaFromDdl(Connection)
+-- see net.jakubholy.testing.dbunit.DatabaseCreator#main.
+
+-- Replace the text below with whatever you need.
+create schema my_test_schema;
+
+create table my_test_schema.my_test_table (
+ id int primary key
+ , some_text varchar(10));
@@ -0,0 +1,47 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--DOCTYPE dataset SYSTEM 'testData/dbunit-test_data_set.dtd'-->
+<!--
+ See DbUnit's XmlDataSet.
+ Database definition for AbstractEmbeddedDbTestCase
+ - uses DbUnit to load the data into an embedded derby DB -
+ To connect to the DB: user sa, psw emty, url
+ jdbc:derby:<absolute path to this project>\testData\testDB
+
+ Note: Only 1 connection to the Derby DB at a time is allowed!!!!
+
+ IMPORTANT
+ =========
+ 1) You must have <table> entry for any table you want DbUnit to clear for you.
+ Even if you don't want DbUnit to insert any data you must still declare at least
+ <table name="yourSchema.yourTable" /> for the table yourSchema.yourTable to be emptied during test setUp().
+ 2) When using a column that contains XML value, you may use <![CDATA[ to supply the value.
+ But there should be no space within '<value><![CDATA[<?xml ...' otherwise you may get Xerces'
+ exception 'The processing instruction target matching "[xX][mM][lL]" is not allowed.'
+
+ ABOUT FILE FORMAT
+ =================
+ This format is better than flat data set because it describes explicitly the
+ columns though it's also more verbose.
+
+ Also, thanks to setting the DbUnit feature QUALIFIED_TABLE_NAMES to true in the test,
+ we can use fully qualified table names and thus have tables in different schemas.
+
+ HOW TO CREATE
+ =============
+ To create a DbUnit data set like this you've several options:
+ 1. Write it manually.
+ 2. Use DbUnit itself to connect to a DB, load a data set and store it into a XmlDataSet.
+ 3. Use the Eclipse plugin QuantumDB and its ability to export table into DbUnit data set format.
+ 4. Use Jailer (http://jailer.sourceforge.net/home.htm) - it can export also only a sample of
+ a DB while preserving referential integrity.
+ -->
+<dataset>
+
+ <!--
+ ######### TABLES TO CLEAR WITHOUT INSERTING ANY DATA DURING TestCase#setUp() #########
+ Provide names of the tables whose content shall be dropped prior to running a test method.
+ You don't need to list its columns.
+ -->
+ <table name="my_test_schema.my_test_table" />
+
+</dataset>

0 comments on commit c32264c

Please sign in to comment.