Added batchUpdate method taking a Collection, a batch size and a ParameterizedPreparedStatementSetter as arguments (SPR-6334)

This commit is contained in:
Thomas Risberg
2011-06-05 16:42:24 +00:00
parent cfb387383b
commit 0adcb2ad2e
5 changed files with 290 additions and 44 deletions

View File

@@ -16,6 +16,7 @@
package org.springframework.jdbc.core; package org.springframework.jdbc.core;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -1005,6 +1006,18 @@ public interface JdbcOperations {
*/ */
public int[] batchUpdate(String sql, List<Object[]> batchArgs, int[] argTypes); public int[] batchUpdate(String sql, List<Object[]> batchArgs, int[] argTypes);
/**
* Execute multiple batches using the supplied SQL statement with the collect of supplied arguments.
* The arguments' values will be set using the ParameterizedPreparedStatementSetter.
* Each batch should be of size indicated in 'batchSize'.
* @param sql the SQL statement to execute.
* @param batchArgs the List of Object arrays containing the batch of arguments for the query
* @param argTypes SQL types of the arguments
* (constants from <code>java.sql.Types</code>)
* @return an array containing for each batch another array containing the numbers of rows affected
* by each update in the batch
*/
public <T> int[][] batchUpdate(String sql, Collection<T> batchArgs, int batchSize, ParameterizedPreparedStatementSetter<T> pss);
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
// Methods dealing with callable statements // Methods dealing with callable statements

View File

@@ -28,6 +28,7 @@ import java.sql.SQLException;
import java.sql.SQLWarning; import java.sql.SQLWarning;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -930,6 +931,58 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
return BatchUpdateUtils.executeBatchUpdate(sql, batchArgs, argTypes, this); return BatchUpdateUtils.executeBatchUpdate(sql, batchArgs, argTypes, this);
} }
/*
* (non-Javadoc)
* @see org.springframework.jdbc.core.JdbcOperations#batchUpdate(java.lang.String, java.util.Collection, int, org.springframework.jdbc.core.ParameterizedPreparedStatementSetter)
*
* Contribution by Nicolas Fabre
*/
public <T> int[][] batchUpdate(String sql, final Collection<T> batchArgs, final int batchSize, final ParameterizedPreparedStatementSetter<T> pss) {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL batch update [" + sql + "] with a batch size of " + batchSize);
}
return execute(sql, new PreparedStatementCallback<int[][]>() {
public int[][] doInPreparedStatement(PreparedStatement ps) throws SQLException {
List<int[]> rowsAffected = new ArrayList<int[]>();
try {
boolean batchSupported = true;
if (!JdbcUtils.supportsBatchUpdates(ps.getConnection())) {
batchSupported = false;
logger.warn("JDBC Driver does not support Batch updates; resorting to single statement execution");
}
int n = 0;
for (T obj : batchArgs) {
pss.setValues(ps, obj);
n++;
if (batchSupported) {
ps.addBatch();
if (n % batchSize == 0 || n == batchArgs.size()) {
if (logger.isDebugEnabled()) {
int batchIdx = (n % batchSize == 0) ? n / batchSize : (n / batchSize) + 1;
int items = n - ((n % batchSize == 0) ? n / batchSize - 1 : (n / batchSize)) * batchSize;
logger.debug("Sending SQL batch update #" + batchIdx + " with " + items + " items");
}
rowsAffected.add(ps.executeBatch());
}
}
else {
int i = ps.executeUpdate();
rowsAffected.add(new int[] {i});
}
}
int[][] result = new int[rowsAffected.size()][];
for (int i = 0; i < result.length; i++) {
result[i] = rowsAffected.get(i);
}
return result;
} finally {
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}
});
}
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
// Methods dealing with callable statements // Methods dealing with callable statements

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.jdbc.core;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* Parameterized callback interface used by the {@link JdbcTemplate} class for batch updates.
*
* <p>This interface sets values on a {@link java.sql.PreparedStatement} provided
* by the JdbcTemplate class, for each of a number of updates in a batch using the
* same SQL. Implementations are responsible for setting any necessary parameters.
* SQL with placeholders will already have been supplied.
*
* <p>Implementations <i>do not</i> need to concern themselves with SQLExceptions
* that may be thrown from operations they attempt. The JdbcTemplate class will
* catch and handle SQLExceptions appropriately.
*
* @author Nicolas Fabre
* @author Thomas Risberg
* @since 3.1
* @see JdbcTemplate#batchUpdate(String sql, Collection<T> objs, int batchSize, ParameterizedPreparedStatementSetter<T> pss)
*/
public interface ParameterizedPreparedStatementSetter<T> {
/**
* Set parameter values on the given PreparedStatement.
*
* @param ps the PreparedStatement to invoke setter methods on
* @param argument the object containing the values to be set
* @throws SQLException if a SQLException is encountered (i.e. there is no need to catch SQLException)
*/
void setValues(PreparedStatement ps, T argument) throws SQLException;
}

View File

@@ -26,6 +26,7 @@ import java.sql.SQLWarning;
import java.sql.Statement; import java.sql.Statement;
import java.sql.Types; import java.sql.Types;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -1189,6 +1190,76 @@ public class JdbcTemplateTests extends AbstractJdbcTests {
BatchUpdateTestHelper.verifyBatchUpdateMocks(ctrlPreparedStatement, ctrlDatabaseMetaData); BatchUpdateTestHelper.verifyBatchUpdateMocks(ctrlPreparedStatement, ctrlDatabaseMetaData);
} }
public void testBatchUpdateWithCollectionOfObjects() throws Exception {
final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?";
final List<Integer> ids = new ArrayList<Integer>();
ids.add(Integer.valueOf(100));
ids.add(Integer.valueOf(200));
ids.add(Integer.valueOf(300));
final int[] rowsAffected1 = new int[] { 1, 2 };
final int[] rowsAffected2 = new int[] { 3 };
MockControl ctrlPreparedStatement = MockControl.createControl(PreparedStatement.class);
PreparedStatement mockPreparedStatement = (PreparedStatement) ctrlPreparedStatement.getMock();
mockPreparedStatement.getConnection();
ctrlPreparedStatement.setReturnValue(mockConnection);
mockPreparedStatement.setInt(1, ids.get(0));
ctrlPreparedStatement.setVoidCallable();
mockPreparedStatement.addBatch();
ctrlPreparedStatement.setVoidCallable();
mockPreparedStatement.setInt(1, ids.get(1));
ctrlPreparedStatement.setVoidCallable();
mockPreparedStatement.addBatch();
ctrlPreparedStatement.setVoidCallable();
mockPreparedStatement.setInt(1, ids.get(2));
ctrlPreparedStatement.setVoidCallable();
mockPreparedStatement.executeBatch();
ctrlPreparedStatement.setReturnValue(rowsAffected1);
mockPreparedStatement.addBatch();
ctrlPreparedStatement.setVoidCallable();
mockPreparedStatement.executeBatch();
ctrlPreparedStatement.setReturnValue(rowsAffected2);
if (debugEnabled) {
mockPreparedStatement.getWarnings();
ctrlPreparedStatement.setReturnValue(null);
}
mockPreparedStatement.close();
ctrlPreparedStatement.setVoidCallable();
MockControl ctrlDatabaseMetaData = MockControl.createControl(DatabaseMetaData.class);
DatabaseMetaData mockDatabaseMetaData = (DatabaseMetaData) ctrlDatabaseMetaData.getMock();
mockDatabaseMetaData.getDatabaseProductName();
ctrlDatabaseMetaData.setReturnValue("MySQL");
mockDatabaseMetaData.supportsBatchUpdates();
ctrlDatabaseMetaData.setReturnValue(true);
mockConnection.prepareStatement(sql);
ctrlConnection.setReturnValue(mockPreparedStatement);
mockConnection.getMetaData();
ctrlConnection.setReturnValue(mockDatabaseMetaData, 2);
ctrlPreparedStatement.replay();
ctrlDatabaseMetaData.replay();
replay();
ParameterizedPreparedStatementSetter setter = new ParameterizedPreparedStatementSetter<Integer>() {
public void setValues(PreparedStatement ps, Integer argument) throws SQLException {
ps.setInt(1, argument.intValue());
}
};
JdbcTemplate template = new JdbcTemplate(mockDataSource, false);
int[][] actualRowsAffected = template.batchUpdate(sql, ids, 2, setter);
assertTrue("executed 2 updates", actualRowsAffected[0].length == 2);
assertEquals(rowsAffected1[0], actualRowsAffected[0][0]);
assertEquals(rowsAffected1[1], actualRowsAffected[0][1]);
assertEquals(rowsAffected2[0], actualRowsAffected[1][0]);
ctrlPreparedStatement.verify();
ctrlDatabaseMetaData.verify();
}
public void testCouldntGetConnectionOrExceptionTranslator() throws SQLException { public void testCouldntGetConnectionOrExceptionTranslator() throws SQLException {
SQLException sex = new SQLException("foo", "07xxx"); SQLException sex = new SQLException("foo", "07xxx");

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"> "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<chapter id="jdbc"> <chapter id="jdbc">
@@ -8,9 +8,9 @@
<title>Introduction to Spring Framework JDBC</title> <title>Introduction to Spring Framework JDBC</title>
<para>The value-add provided by the Spring Framework JDBC abstraction is <para>The value-add provided by the Spring Framework JDBC abstraction is
perhaps best shown by the sequence of actions outlined in the table perhaps best shown by the sequence of actions outlined in the table below.
below. The table shows what actions Spring will take care of and which The table shows what actions Spring will take care of and which actions
actions are the responsibility of you, the application developer.<!--Is this sequence correct, as far as what developer does and doesn't do? Does it adhere to info in the rest of the chapter? are the responsibility of you, the application developer.<!--Is this sequence correct, as far as what developer does and doesn't do? Does it adhere to info in the rest of the chapter?
--><!--How does JDBC know what connection parameters are if a human does not at some point define them?--><!--TR: OK. I have rewritten this as a table indicating who has what responsibility. --></para> --><!--How does JDBC know what connection parameters are if a human does not at some point define them?--><!--TR: OK. I have rewritten this as a table indicating who has what responsibility. --></para>
<table align="left" width=""> <table align="left" width="">
@@ -1036,7 +1036,8 @@ public class ExecuteAnUpdate {
<section id="jdbc-auto-genereted-keys"> <section id="jdbc-auto-genereted-keys">
<title>Retrieving auto-generated keys</title> <title>Retrieving auto-generated keys</title>
<para>An <methodname>update()</methodname> convenience method supports<!--Give name of this method. Also indicate *what* is acquiring the primary keys. TR: Changed to *retrieval*. <para>An <methodname>update()</methodname> convenience method
supports<!--Give name of this method. Also indicate *what* is acquiring the primary keys. TR: Changed to *retrieval*.
The name of the method is *update*.--> the retrieval of primary keys generated The name of the method is *update*.--> the retrieval of primary keys generated
by the database. This support is part of the JDBC 3.0 standard; see by the database. This support is part of the JDBC 3.0 standard; see
Chapter 13.6 of the specification for details. The method takes a Chapter 13.6 of the specification for details. The method takes a
@@ -1368,7 +1369,7 @@ dataSource.setPassword("");</programlisting>
<classname>SimpleJdbcTemplate</classname>.</para> <classname>SimpleJdbcTemplate</classname>.</para>
<section id="jdbc-advanced-classic"> <section id="jdbc-advanced-classic">
<title>Batch operations with the JdbcTemplate</title> <title>Basic batch operations with the JdbcTemplate</title>
<para>You accomplish <classname>JdbcTemplate</classname> batch <para>You accomplish <classname>JdbcTemplate</classname> batch
processing by implementing two methods of a special interface, processing by implementing two methods of a special interface,
@@ -1418,11 +1419,12 @@ dataSource.setPassword("");</programlisting>
</section> </section>
<section id="jdbc-advanced-simple"> <section id="jdbc-advanced-simple">
<title>Batch operations with the SimpleJdbcTemplate</title> <title>Batch operations with a List of objects</title>
<para>The <classname>SimpleJdbcTemplate</classname> provides an <para>Both the <classname>JdbcTemplate</classname> and the
alternate way of providing the batch update. Instead of implementing a <classname>NamedParameterJdbcTemplate</classname> provides an alternate
special batch interface, you provide all parameter values in the call. way of providing the batch update. Instead of implementing a special
batch interface, you provide all parameter values in the call as a list.
The framework loops over these values and uses an internal prepared The framework loops over these values and uses an internal prepared
statement setter. The API varies depending on whether you use named statement setter. The API varies depending on whether you use named
parameters. For the named parameters you provide an array of parameters. For the named parameters you provide an array of
@@ -1435,15 +1437,15 @@ dataSource.setPassword("");</programlisting>
<para>This example shows a batch update using named parameters:</para> <para>This example shows a batch update using named parameters:</para>
<para><programlisting language="java">public class JdbcActorDao implements ActorDao { <para><programlisting language="java">public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate; private NamedParameterTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) { public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
} }
public int[] batchUpdate(final List&lt;Actor&gt; actors) { public int[] batchUpdate(final List&lt;Actor&gt; actors) {
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(actors.toArray()); SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(actors.toArray());
int[] updateCounts = simpleJdbcTemplate.batchUpdate( int[] updateCounts = namedParameterJdbcTemplate.batchUpdate(
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id", "update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
batch); batch);
return updateCounts; return updateCounts;
@@ -1459,10 +1461,10 @@ dataSource.setPassword("");</programlisting>
<para>The same example using classic JDBC "?" placeholders:</para> <para>The same example using classic JDBC "?" placeholders:</para>
<para><programlisting language="java">public class JdbcActorDao implements ActorDao { <para><programlisting language="java">public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate; private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) { public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); this.jdbcTemplate = new JdbcTemplate(dataSource);
} }
public int[] batchUpdate(final List&lt;Actor&gt; actors) { public int[] batchUpdate(final List&lt;Actor&gt; actors) {
@@ -1474,17 +1476,79 @@ dataSource.setPassword("");</programlisting>
actor.getId()}; actor.getId()};
batch.add(values); batch.add(values);
} }
int[] updateCounts = simpleJdbcTemplate.batchUpdate( int[] updateCounts = jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?", "update t_actor set first_name = ?, last_name = ? where id = ?",
batch); batch);
return updateCounts; return updateCounts;
} }
// ... additional methods // ... additional methods
}</programlisting>All batch update methods return an int array containing the }</programlisting>All of the above batch update methods return an int array
number of affected rows for each batch entry. This count is reported by containing the number of affected rows for each batch entry. This count
the JDBC driver. If the count is not available, the JDBC driver returns is reported by the JDBC driver. If the count is not available, the JDBC
a -2 value.</para> driver returns a -2 value.</para>
</section>
<section id="jdbc-advanced-simple">
<title>Batch operations with multiple batches</title>
<para>The last example of a batch update deals with batches that are so
large that you want to break them up into several smaller batches. You
can of course do this with the methods mentioned above by making
multiple calls to the <classname>batchUpdate</classname> method, but
there is now a more convenient method. This method takes, in addition to
the SQL statement, a Collection of objects containing the parameters,
the number of updates to make for each batch and a
<classname>ParameterizedPreparedStatementSetter</classname> to set the
values for the parameters of the prepared statement. The framework loops
over the provided values and breaks the update calls into batches of the
size specified. </para>
<para>This example shows a batch update using a batch size of
100:</para>
<para><programlisting language="java">public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public int[][] batchUpdate(final Collection&lt;Actor&gt; actors) {
Collection&lt;Object[]&gt; batch = new ArrayList&lt;Object[]&gt;();
for (Actor actor : actors) {
Object[] values = new Object[] {
actor.getFirstName(),
actor.getLastName(),
actor.getId()};
batch.add(values);
}
int[][] updateCounts = jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
actors,
100,
new ParameterizedPreparedStatementSetter&lt;Actor&gt;() {
public void setValues(PreparedStatement ps, Actor argument) throws SQLException {
ps.setString(1, argument.getFirstName());
ps.setString(2, argument.getLastName());
ps.setLong(3, argument.getId().longValue());
}
} );
return updateCounts;
}
// ... additional methods
}</programlisting>The batch update methods for this call returns an array of
int arrays containing an array entry for each batch with an array of the
number of affected rows for each update. The top level array's length
indicates the number of batches executed and the second level array's
length indicates the number of updates in that batch. The number of
updates in each batch should be the the batch size provided for all
batches except for the last one that might be less, depending on the
total number of updat objects provided. The update count for each update
stament is the one reported by the JDBC driver. If the count is not
available, the JDBC driver returns a -2 value.</para>
</section> </section>
</section> </section>
@@ -1715,9 +1779,9 @@ END;</programlisting>The <code>in_id</code> parameter contains the
<para>The <classname>SimpleJdbcCall</classname> is declared in a similar <para>The <classname>SimpleJdbcCall</classname> is declared in a similar
manner to the <classname>SimpleJdbcInsert</classname>. You should manner to the <classname>SimpleJdbcInsert</classname>. You should
instantiate and configure the class in the initialization method of your instantiate and configure the class in the initialization method of your
data access layer. Compared to the StoredProcedure class, you don't data access layer. Compared to the StoredProcedure class, you don't have
have to create a subclass and you don't have to declare parameters that to create a subclass and you don't have to declare parameters that can
can be looked up in the database metadata. <!--Reword preceding: You need not subclass *what?* and you declare *what* in init method? TR: Revised, pplease review.-->Following be looked up in the database metadata. <!--Reword preceding: You need not subclass *what?* and you declare *what* in init method? TR: Revised, pplease review.-->Following
is an example of a SimpleJdbcCall configuration using the above stored is an example of a SimpleJdbcCall configuration using the above stored
procedure. The only configuration option, in addition to the procedure. The only configuration option, in addition to the
<classname>DataSource</classname>, is the name of the stored <classname>DataSource</classname>, is the name of the stored
@@ -2651,9 +2715,9 @@ clobReader.close();</programlisting>
<para>In addition to the primitive values in the value list, you can <para>In addition to the primitive values in the value list, you can
create a <classname>java.util.List</classname> of object arrays. This create a <classname>java.util.List</classname> of object arrays. This
list would support multiple expressions defined for the <code>in</code> list would support multiple expressions defined for the <code>in</code>
clause such as <code>select * from T_ACTOR where (id, last_name) in clause such as <code>select * from T_ACTOR where (id, last_name) in ((1,
((1, 'Johnson'), (2, 'Harrop'))</code>. This 'Johnson'), (2, 'Harrop'))</code>. This of course requires that your
of course requires that your database supports this syntax.</para> database supports this syntax.</para>
</section> </section>
<section id="jdbc-complex-types"> <section id="jdbc-complex-types">
@@ -2920,8 +2984,7 @@ public class DataAccessUnitTestTemplate {
existing data, the XML namespace provides a couple more options. The existing data, the XML namespace provides a couple more options. The
first is flag to switch the initialization on and off. This can be set first is flag to switch the initialization on and off. This can be set
according to the environment (e.g. to pull a boolean value from system according to the environment (e.g. to pull a boolean value from system
properties or an environment bean), e.g. properties or an environment bean), e.g. <programlisting>&lt;jdbc:initialize-database data-source="dataSource"
<programlisting>&lt;jdbc:initialize-database data-source="dataSource"
<emphasis role="bold">enabled="#{systemProperties.INITIALIZE_DATABASE}"</emphasis>&gt; <emphasis role="bold">enabled="#{systemProperties.INITIALIZE_DATABASE}"</emphasis>&gt;
&lt;jdbc:script location="..."/&gt; &lt;jdbc:script location="..."/&gt;
&lt;/jdbc:initialize-database&gt;</programlisting></para> &lt;/jdbc:initialize-database&gt;</programlisting></para>
@@ -2979,23 +3042,20 @@ public class DataAccessUnitTestTemplate {
<para>The first option might be easy if the application is in your <para>The first option might be easy if the application is in your
control, and not otherwise. Some suggestions for how to implement this control, and not otherwise. Some suggestions for how to implement this
are<itemizedlist> are<itemizedlist>
<listitem> <listitem>
<para>Make the cache initialize lazily on first usage, which <para>Make the cache initialize lazily on first usage, which
improves application startup time</para> improves application startup time</para>
</listitem> </listitem>
<listitem> <listitem>
<para>Have your cache or a separate component that <para>Have your cache or a separate component that initializes
initializes the cache implement <code>Lifecycle</code> or the cache implement <code>Lifecycle</code> or
<code>SmartLifecycle</code>. When the application context <code>SmartLifecycle</code>. When the application context starts
starts up a <code>SmartLifecycle</code> can be automatically up a <code>SmartLifecycle</code> can be automatically started if
started if its <code>autoStartup</code> flag is set, its <code>autoStartup</code> flag is set, and a
and a <code>Lifecycle</code> can be started <code>Lifecycle</code> can be started manually by calling
manually by calling
<code>ConfigurableApplicationContext.start()</code> on the <code>ConfigurableApplicationContext.start()</code> on the
enclosing context. enclosing context.</para>
</para>
</listitem> </listitem>
<listitem> <listitem>
@@ -3003,10 +3063,9 @@ public class DataAccessUnitTestTemplate {
custom observer mechanism to trigger the cache initialization. custom observer mechanism to trigger the cache initialization.
<code>ContextRefreshedEvent</code> is always published by the <code>ContextRefreshedEvent</code> is always published by the
context when it is ready for use (after all beans have been context when it is ready for use (after all beans have been
initialized), so that is often a useful hook (this is initialized), so that is often a useful hook (this is how the
how the <code>SmartLifecycle</code> works by default).</para> <code>SmartLifecycle</code> works by default).</para>
</listitem> </listitem>
</itemizedlist></para> </itemizedlist></para>
<para>The second option can also be easy. Some suggestions on how to <para>The second option can also be easy. Some suggestions on how to