diff --git a/CHANGELOG.md b/CHANGELOG.md index b60e3a50..92383fa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- options for statement configuration in BasicDAOHelper + ## [8.7.2] - 2025-11-20 +### Changed + - BasicDAOHelper has method to set query timeout ## [8.7.1] - 2025-10-31 diff --git a/fj-core/src/main/java/org/fugerit/java/core/db/dao/DAORuntimeException.java b/fj-core/src/main/java/org/fugerit/java/core/db/dao/DAORuntimeException.java index aed0f39a..ca02e279 100644 --- a/fj-core/src/main/java/org/fugerit/java/core/db/dao/DAORuntimeException.java +++ b/fj-core/src/main/java/org/fugerit/java/core/db/dao/DAORuntimeException.java @@ -20,6 +20,7 @@ */ package org.fugerit.java.core.db.dao; +import lombok.extern.slf4j.Slf4j; import org.fugerit.java.core.function.UnsafeSupplier; import org.fugerit.java.core.function.UnsafeVoid; import org.fugerit.java.core.lang.ex.ExConverUtils; @@ -29,6 +30,7 @@ * @author Fugerit * */ +@Slf4j public class DAORuntimeException extends RuntimeException { /* @@ -88,5 +90,14 @@ public static void apply( UnsafeVoid fun ) { throw convertEx( e ); } } + + public static void applySilent( UnsafeVoid fun ) { + try { + fun.apply(); + } catch (Exception e) { + String message = String.format( "Exception on DAORuntimeException.applySilent() %s", e.getMessage() ); + log.warn( message, e ); + } + } } diff --git a/fj-core/src/main/java/org/fugerit/java/core/db/daogen/BasicDAOHelper.java b/fj-core/src/main/java/org/fugerit/java/core/db/daogen/BasicDAOHelper.java index 895fd3d0..d9a5d4d7 100644 --- a/fj-core/src/main/java/org/fugerit/java/core/db/daogen/BasicDAOHelper.java +++ b/fj-core/src/main/java/org/fugerit/java/core/db/daogen/BasicDAOHelper.java @@ -8,15 +8,12 @@ import java.sql.Statement; import java.util.ArrayList; import java.util.List; +import java.util.function.BiFunction; -import org.fugerit.java.core.db.dao.DAOException; -import org.fugerit.java.core.db.dao.DAOHelper; -import org.fugerit.java.core.db.dao.DAOUtilsNG; -import org.fugerit.java.core.db.dao.FieldFactory; -import org.fugerit.java.core.db.dao.FieldList; -import org.fugerit.java.core.db.dao.RSExtractor; +import org.fugerit.java.core.db.dao.*; import org.fugerit.java.core.lang.helpers.StringUtils; import org.fugerit.java.core.log.LogObject; +import org.fugerit.java.core.util.ObjectUtils; import org.fugerit.java.core.util.checkpoint.CheckpointUtils; import org.slf4j.Logger; @@ -24,11 +21,12 @@ /** *

BasicDAOHelper for database operation.

- * - * @author fugerit + * + * @author Matteo Franci a.k.a. Fugerit * * @param the type returned by this DAOHelper - * + * + * NOTE: this object is not thread-safe */ @Slf4j public class BasicDAOHelper implements LogObject { @@ -46,23 +44,28 @@ public static String fieldListToString( FieldList fl ) { for ( int k=1; k statementHelper; public BasicDAOHelper( DAOContext daoContext ) { - this( daoContext, null ); + this( daoContext, (BiFunction)null ); } public BasicDAOHelper(DAOContext daoContext, Integer queryTimeout) { + this( daoContext, StatementHelperLibrary.hewHelperSafeSilent( StatementHelperLibrary.newHelperWithQueryTimeout( queryTimeout ) ) ); + } + + public BasicDAOHelper(DAOContext daoContext, BiFunction statementHelper) { this.daoContext = daoContext; - this.queryTimeout = queryTimeout; + this.statementHelper = ObjectUtils.objectWithDefault( statementHelper, StatementHelperLibrary.DEFAULT_STATEMENT_HELPER ); } public FieldList newFieldList() { @@ -78,16 +81,13 @@ public T loadOneHelper( SelectHelper query, RSExtractor re ) throws DAOExcep } return res; } - + public void loadAllHelper( List l, SelectHelper query, RSExtractor re ) throws DAOException { this.loadAllHelper( l, query.getQueryContent(), query.getFields(), re ); } - private PreparedStatement prepareStatement( PreparedStatement ps ) throws SQLException { - if ( this.queryTimeout != null ) { - ps.setQueryTimeout( this.queryTimeout ); - } - return ps; + private PreparedStatement prepareStatement( PreparedStatement ps, DAOContext context ) { + return this.statementHelper.apply( ps, context ); } public void loadAllHelper( List l, String query, FieldList fields, RSExtractor re ) throws DAOException { @@ -99,7 +99,7 @@ public void loadAllHelper( List l, String query, FieldList fields, RSExtracto log.debug( "queryId:'{}', loadAll RSExtractor : '{}'", queryId, re); Connection conn = this.daoContext.getConnection(); int i=0; - try ( PreparedStatement ps = this.prepareStatement( conn.prepareStatement( query ) ) ) { + try ( PreparedStatement ps = this.prepareStatement( conn.prepareStatement( query ), this.daoContext ) ) { DAOHelper.setAll( queryId, ps, fields , log ); long executeStart = System.currentTimeMillis(); try ( ResultSet rs = ps.executeQuery() ) { @@ -117,11 +117,11 @@ public void loadAllHelper( List l, String query, FieldList fields, RSExtracto } log.debug("queryId:{}, loadAll END list : '{}'", queryId, l.size()); } - + private int updateWorker( String queryId, FieldList fields, String query , long startTime) throws DAOException { int res = 0; Connection conn = this.daoContext.getConnection(); - try ( PreparedStatement ps = this.prepareStatement( conn.prepareStatement( query ) ) ) { + try ( PreparedStatement ps = this.prepareStatement( conn.prepareStatement( query ), this.daoContext ) ) { DAOHelper.setAll( queryId, ps, fields , log ); long executeStart = System.currentTimeMillis(); res = ps.executeUpdate(); @@ -132,7 +132,7 @@ private int updateWorker( String queryId, FieldList fields, String query , long } return res; } - + public int update( QueryHelper queryHelper ) throws DAOException { int res = 0; try { @@ -149,17 +149,17 @@ public int update( QueryHelper queryHelper ) throws DAOException { } return res; } - + private String createSequenceQuery( String sequence ) { return " SELECT "+sequence+".NEXTVAL FROM DUAL"; } - + public BigDecimal newSequenceValue( String sequence ) throws DAOException { BigDecimal id = null; String sql = this.createSequenceQuery(sequence); log.info( "newSequenceValue() sql > "+sql ); try ( Statement stm = this.daoContext.getConnection().createStatement(); - ResultSet rs = stm.executeQuery( sql ) ) { + ResultSet rs = stm.executeQuery( sql ) ) { if ( rs.next() ) { id = rs.getBigDecimal( 1 ); } @@ -168,35 +168,34 @@ public BigDecimal newSequenceValue( String sequence ) throws DAOException { } return id; } - + public SelectHelper newSelectHelper( String queryView, String tableName ) { SelectHelper helper = null; if ( StringUtils.isNotEmpty( queryView ) ) { helper = new SelectHelper( tableName , this.newFieldList() ); - helper.appendToQuery( " SELECT * FROM ( "+queryView+" ) v " ); + helper.appendToQuery( " SELECT * FROM ( "+queryView+" ) v " ); } else { helper = newSelectHelper( tableName ); } return helper; } - + public SelectHelper newSelectHelper( String tableName ) { SelectHelper query = new SelectHelper( tableName , this.newFieldList() ); query.initSelectEntity(); return query; } - + public InsertHelper newInsertHelper( String tableName ) { return new InsertHelper( tableName , this.newFieldList() ); } - + public UpdateHelper newUpdateHelper( String tableName ) { return new UpdateHelper( tableName , this.newFieldList() ); } - + public DeleteHelper newDeleteHelper( String tableName ) { return new DeleteHelper( tableName , this.newFieldList() ); } - - + } diff --git a/fj-core/src/main/java/org/fugerit/java/core/db/daogen/StatementHelperLibrary.java b/fj-core/src/main/java/org/fugerit/java/core/db/daogen/StatementHelperLibrary.java new file mode 100644 index 00000000..419d2893 --- /dev/null +++ b/fj-core/src/main/java/org/fugerit/java/core/db/daogen/StatementHelperLibrary.java @@ -0,0 +1,85 @@ +package org.fugerit.java.core.db.daogen; + +import org.fugerit.java.core.db.dao.DAORuntimeException; +import org.fugerit.java.core.function.SafeFunction; +import org.fugerit.java.core.function.SimpleValue; +import org.fugerit.java.core.util.ObjectUtils; + +import java.sql.PreparedStatement; +import java.util.function.BiFunction; + +public class StatementHelperLibrary { + + private StatementHelperLibrary() {} + + private static final String ATT_DAO_CONTEXT_PATTERN = "DaoContext.StatementHelper.%s.ATT_NAME"; + + public static final String ATT_DAO_CONTEXT_QUERY_TIMEOUT = String.format( ATT_DAO_CONTEXT_PATTERN, "queryTimeout" ); + + public static final String ATT_DAO_CONTEXT_FETCH_SIZE = String.format( ATT_DAO_CONTEXT_PATTERN, "fetchSize" ); + + public static final BiFunction + DO_NOTHING_STATEMENT_HELPER = ( p, c ) -> p; + + public static final BiFunction + QUERY_TIMEOUT_FETCH_SIZE_STATEMENT_HELPER = newHelperPipeline( newHelperWithQueryTimeout( null ), newHelperWithFetchSize( null ) ); + + public static final BiFunction + DEFAULT_STATEMENT_HELPER = hewHelperSafeSilent( QUERY_TIMEOUT_FETCH_SIZE_STATEMENT_HELPER ); + + private static T resolve( DAOContext daoContext, String key, T defaultValue ) { + return ObjectUtils.objectWithDefault( (T)daoContext.getAttribute( key ), defaultValue ); + } + + public static DAOContext withQueryTimeout( DAOContext daoContext, Integer queryTimeout ) { + if ( queryTimeout != null ) { + daoContext.setAttribute( ATT_DAO_CONTEXT_QUERY_TIMEOUT, queryTimeout ); + } + return daoContext; + } + + public static DAOContext withFetchSize( DAOContext daoContext, Integer fetchSize ) { + if ( fetchSize != null ) { + daoContext.setAttribute( ATT_DAO_CONTEXT_FETCH_SIZE, fetchSize ); + } + return daoContext; + } + + public static BiFunction newHelperWithQueryTimeout( Integer queryTimeoutSeconds ) { + return (ps, daoContext) -> { + Integer resolvedQueryTimeoutSeconds = resolve( daoContext, ATT_DAO_CONTEXT_QUERY_TIMEOUT, queryTimeoutSeconds ); + SafeFunction.applyIfNotNull( resolvedQueryTimeoutSeconds, + () -> ps.setQueryTimeout( resolvedQueryTimeoutSeconds.intValue() ) ); + return ps; + }; + } + + public static BiFunction newHelperWithFetchSize( Integer fetchSize ) { + return (ps, daoContext) -> { + Integer resolvedFetchSize = resolve( daoContext, ATT_DAO_CONTEXT_FETCH_SIZE, fetchSize ); + SafeFunction.applyIfNotNull( resolvedFetchSize, + () -> ps.setFetchSize( resolvedFetchSize.intValue() ) ); + return ps; + }; + } + + public static BiFunction newHelperPipeline( + BiFunction... helpers) { + return (preparedStatement, daoContext) -> { + PreparedStatement ps = preparedStatement; + for ( BiFunction helper : helpers ) { + ps = helper.apply(ps, daoContext); + } + return ps; + }; + } + public static BiFunction hewHelperSafeSilent( + BiFunction helper) { + return (ps, daoContext) -> { + SimpleValue res = new SimpleValue<>( ps ); + DAORuntimeException.applySilent( () -> res.setValue( helper.apply(ps, daoContext ) ) ); + return res.getValue(); + }; + } + +} diff --git a/fj-core/src/test/java/test/org/fugerit/java/core/db/dao/TestDAORuntimeException.java b/fj-core/src/test/java/test/org/fugerit/java/core/db/dao/TestDAORuntimeException.java index 3e0783e7..08d428f0 100644 --- a/fj-core/src/test/java/test/org/fugerit/java/core/db/dao/TestDAORuntimeException.java +++ b/fj-core/src/test/java/test/org/fugerit/java/core/db/dao/TestDAORuntimeException.java @@ -1,11 +1,23 @@ package test.org.fugerit.java.core.db.dao; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Property; import org.fugerit.java.core.db.dao.DAORuntimeException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Slf4j class TestDAORuntimeException { @Test @@ -13,6 +25,56 @@ void testApply() { Assertions.assertThrows( DAORuntimeException.class ,() -> DAORuntimeException.apply( () -> { throw new SQLException( "junit test scenario" ); } ) ); } + @Test + void testApplySilent() { + // Create a custom appender to capture log events + List logEvents = new ArrayList<>(); + + AbstractAppender testAppender = new AbstractAppender("TestAppender", null, null, true, Property.EMPTY_ARRAY) { + @Override + public void append(LogEvent event) { + logEvents.add(event.toImmutable()); + } + }; + testAppender.start(); + + // Get the logger and add our appender + Logger logger = (Logger) LogManager.getLogger(DAORuntimeException.class); + logger.addAppender(testAppender); + logger.setAdditive(true); + + String errorMessage = "junit test scenario apply silent"; + + try { + // Execute the method under test + DAORuntimeException.applySilent(() -> { + throw new SQLException(errorMessage); + }); + + // Verify log event was captured + assertEquals(1, logEvents.size()); + + LogEvent logEvent = logEvents.get(0); + + // Verify it's a WARN level + assertEquals(org.apache.logging.log4j.Level.WARN, logEvent.getLevel()); + + // Verify the message + String message = logEvent.getMessage().getFormattedMessage(); + assertTrue(message.contains("Exception on DAORuntimeException.applySilent()")); + assertTrue(message.contains(errorMessage)); + + // Verify the throwable + assertNotNull(logEvent.getThrown()); + assertTrue(logEvent.getThrown() instanceof SQLException); + + } finally { + // Clean up + logger.removeAppender(testAppender); + testAppender.stop(); + } + } + @Test void testGet() { Assertions.assertThrows( DAORuntimeException.class ,() -> DAORuntimeException.get( () -> { throw new SQLException( "junit test scenario" ); } ) ); @@ -52,5 +114,5 @@ void testEx6() { void testEx7() { Assertions.assertNotNull( DAORuntimeException.convertExMethod( "e" , new SQLException( "f" ) ) ); } - + } diff --git a/fj-core/src/test/java/test/org/fugerit/java/core/db/dao/daogen/TestBasicDAOHelper.java b/fj-core/src/test/java/test/org/fugerit/java/core/db/dao/daogen/TestBasicDAOHelper.java index e3247ef2..d8e36529 100644 --- a/fj-core/src/test/java/test/org/fugerit/java/core/db/dao/daogen/TestBasicDAOHelper.java +++ b/fj-core/src/test/java/test/org/fugerit/java/core/db/dao/daogen/TestBasicDAOHelper.java @@ -39,7 +39,6 @@ void testHelpersCreation() { Assertions.assertNotNull( helper.getLogger() ); } } ); - } } diff --git a/fj-core/src/test/java/test/org/fugerit/java/core/db/dao/daogen/TestStatementHelperLibrary.java b/fj-core/src/test/java/test/org/fugerit/java/core/db/dao/daogen/TestStatementHelperLibrary.java new file mode 100644 index 00000000..1324a324 --- /dev/null +++ b/fj-core/src/test/java/test/org/fugerit/java/core/db/dao/daogen/TestStatementHelperLibrary.java @@ -0,0 +1,60 @@ +package test.org.fugerit.java.core.db.dao.daogen; + +import lombok.extern.slf4j.Slf4j; +import org.fugerit.java.core.db.dao.DAOException; +import org.fugerit.java.core.db.dao.FieldList; +import org.fugerit.java.core.db.daogen.*; +import org.fugerit.java.core.db.helpers.DAOID; +import org.fugerit.java.core.function.SafeFunction; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import test.org.fugerit.java.core.db.TestBasicDBHelper; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@Slf4j +class TestStatementHelperLibrary extends TestBasicDBHelper { + + private PreparedStatement worker(DAOContext context, Integer queryTimeoutGeneral, Integer queryTimeoutCustom, Integer fetchSizeGeneral, Integer fetchSizeCustom) throws DAOException, SQLException { + PreparedStatement ps = context.getConnection().prepareStatement( "SELECT * FROM DUAL" ); + return StatementHelperLibrary.newHelperPipeline( + StatementHelperLibrary.DO_NOTHING_STATEMENT_HELPER, + StatementHelperLibrary.newHelperWithQueryTimeout( queryTimeoutGeneral ), + StatementHelperLibrary.newHelperWithFetchSize( fetchSizeGeneral ) + ).apply( ps, StatementHelperLibrary.withQueryTimeout( + StatementHelperLibrary.withFetchSize( context, fetchSizeCustom ), queryTimeoutCustom ) ); + } + + @Test + void testStatementHelperLibrary() throws Exception { + try ( CloseableDAOContextSC context = new CloseableDAOContextSC( newConnection() ) ) { + log.info( "test with no query timeout or fetch size" ); + PreparedStatement ps1 = this.worker( context, null, null, null, null ); + Assertions.assertEquals( 0, ps1.getQueryTimeout() ); + Assertions.assertEquals( 0, ps1.getFetchSize() ); + log.info( "test with general query timeout and fetch size" ); + Integer queryTimeoutGeneral = 10; + Integer fetchSizeGeneral = 20; + PreparedStatement ps2 = this.worker( context, queryTimeoutGeneral, null, fetchSizeGeneral, null ); + Assertions.assertEquals( queryTimeoutGeneral, ps2.getQueryTimeout() ); + Assertions.assertEquals( fetchSizeGeneral, ps2.getFetchSize() ); + log.info( "test with general + custom query timeout and fetch size" ); + Integer queryTimeoutCustom = 30; + Integer fetchSizeCustom = 40; + PreparedStatement ps3 = this.worker( context, queryTimeoutGeneral, queryTimeoutCustom, fetchSizeGeneral, fetchSizeCustom ); + Assertions.assertEquals( queryTimeoutCustom, ps3.getQueryTimeout() ); + Assertions.assertEquals( fetchSizeCustom, ps3.getFetchSize() ); + log.info( "log do nothing" ); + PreparedStatement psDoNothing = StatementHelperLibrary.DO_NOTHING_STATEMENT_HELPER.apply( context.getConnection().prepareStatement( "SELECT * FROM DUAL" ), context ); + Assertions.assertEquals( 0, psDoNothing.getQueryTimeout() ); + Assertions.assertEquals( 0, psDoNothing.getFetchSize() ); + log.info( "log default statement helper" ); + PreparedStatement psDefault = StatementHelperLibrary.DEFAULT_STATEMENT_HELPER.apply( context.getConnection().prepareStatement( "SELECT * FROM DUAL" ), + StatementHelperLibrary.withFetchSize( StatementHelperLibrary.withQueryTimeout( context, queryTimeoutCustom ), fetchSizeCustom ) ); + Assertions.assertEquals( queryTimeoutCustom, psDefault.getQueryTimeout() ); + Assertions.assertEquals( fetchSizeCustom, psDefault.getFetchSize() ); + } + } + +}