Las clases SimpleJdbcInsert y SimpleJdbcCall proporcionan una configuración simplificada utilizando metadatos de la base de datos que se pueden obtener a través del controlador JDBC. Esto significa menos trabajo de configuración, aunque puede anular o deshabilitar el procesamiento de metadatos si prefiere proporcionar detalles en su código.

Insertar datos usando SimpleJdbcInsert

Vamos Comience mirando la clase SimpleJdbcInsert con un número mínimo de parámetros de configuración. Debe crear una instancia de SimpleJdbcInsert en el método de inicialización de la capa de acceso a datos. En este ejemplo, el método de inicialización es el método setDataSource. No es necesario crear una subclase de la clase SimpleJdbcInsert. En su lugar, puede crear una nueva instancia y establecer el nombre de la tabla usando el método withTableName. Los métodos de configuración para esta clase siguen el estilo fluid, que devuelve una instancia de SimpleJdbcInsert, lo que permite encadenar todos los métodos de configuración. El siguiente ejemplo utiliza solo un método de configuración (más adelante se mostrarán ejemplos de múltiples métodos):

Java
public class JdbcActorDao implements ActorDao {
    private SimpleJdbcInsert insertActor;
    public void setDataSource(DataSource dataSource) {
        this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
    }
    public void add(Actor actor) {
        Map<String, Object> parameters = new HashMap<String, Object>(3);
        parameters.put("id", actor.getId());
        parameters.put("first_name", actor.getFirstName());
        parameters.put("last_name", actor.getLastName());
        insertActor.execute(parameters);
    }
    // ... métodos adicionales
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {
    private val insertActor = SimpleJdbcInsert(dataSource).withTableName("t_actor")
    fun add(actor: Actor) {
        val parameters = mutableMapOf<String, Any>()
        parameters["id"] = actor.id
        parameters["first_name"] = actor.firstName
        parameters["last_name"] = actor.lastName
        insertActor.execute(parameters)
    }
    // ... métodos adicionales
}

Usado aquí el método execute toma el habitual java.util.Map como único parámetro. Es importante tener en cuenta que las claves utilizadas para el Map deben coincidir con los nombres de las columnas de la tabla tal como se definen en la base de datos. Esto ocurre porque los metadatos se leen para crear la declaración de inserción real.

Obtener claves generadas automáticamente usando SimpleJdbcInsert

El siguiente ejemplo utiliza la misma inserción que Igual que en el ejemplo anterior, pero en lugar de pasar id, recupera automáticamente la clave generada y luego la configura para un nuevo objeto Actor. Cuando crea un SimpleJdbcInsert, además de especificar el nombre de la tabla, la inserción especifica el nombre de la columna de clave generada utilizando el método usingGeneratedKeyColumns. La siguiente lista muestra cómo se hace esto:

Java
public class JdbcActorDao implements ActorDao {
    private SimpleJdbcInsert insertActor;
    public void setDataSource(DataSource dataSource) {
        this.insertActor = new SimpleJdbcInsert(dataSource)
                .withTableName("t_actor")
                .usingGeneratedKeyColumns("id");
    }
    public void add(Actor actor) {
        Map<String, Object> parameters = new HashMap<String, Object>(2);
        parameters.put("first_name", actor.getFirstName());
        parameters.put("last_name", actor.getLastName());
        Number newId = insertActor.executeAndReturnKey(parameters);
        actor.setId(newId.longValue());
    }
    // ... métodos adicionales
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {
    private val insertActor = SimpleJdbcInsert(dataSource)
            .withTableName("t_actor").usingGeneratedKeyColumns("id")
    fun add(actor: Actor): Actor {
        val parameters = mapOf(
                "first_name" to actor.firstName,
                "last_name" to actor.lastName)
        val newId = insertActor.executeAndReturnKey(parameters);
        return actor.copy(id = newId.toLong())
    }
// ... métodos adicionales
}

La principal diferencia al realizar una inserción usando el El segundo enfoque es que no agrega id a Map, sino que llama al método executeAndReturnKey. Esto devuelve un objeto java.lang.Number que se puede utilizar para crear una instancia del tipo numérico utilizado en la clase de dominio. Aquí no puede confiar en que todas las bases de datos devuelvan una clase Java específica. java.lang.Number es la clase principal que se puede utilizar. Si hay varias columnas generadas automáticamente o los valores generados no son numéricos, entonces puede usar el KeyHolder devuelto por el método executeAndReturnKeyHolder.

Establezca las columnas para SimpleJdbcInsert

Puede limitar las columnas que se insertarán especificando una lista de nombres de columnas usando el método usingColumns, como se muestra en la siguiente ejemplo:

Java
public class JdbcActorDao implements ActorDao {
    private SimpleJdbcInsert insertActor;
    public void setDataSource(DataSource dataSource) {
        this.insertActor = new SimpleJdbcInsert(dataSource)
                .withTableName("t_actor")
                .usingColumns("first_name", "last_name")
                .usingGeneratedKeyColumns("id");
    }
    public void add(Actor actor) {
        Map<String, Object> parameters = new HashMap<String, Object>(2);
        parameters.put("first_name", actor.getFirstName());
        parameters.put("last_name", actor.getLastName());
        Number newId = insertActor.executeAndReturnKey(parameters);
        actor.setId(newId.longValue());
    }
    // ... métodos adicionales
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {
    private val insertActor = SimpleJdbcInsert(dataSource)
            .withTableName("t_actor")
            .usingColumns("first_name", "last_name")
            .usingGeneratedKeyColumns("id")
    fun add(actor: Actor): Actor {
        val parameters = mapOf(
                "first_name" to actor.firstName,
                "last_name" to actor.lastName)
        val newId = insertActor.executeAndReturnKey(parameters);
        return actor.copy(id = newId.toLong())
    }
    // ... métodos adicionales
}

El proceso de inserción es el mismo que si recurrió a metadatos para determinar qué columnas usar.

Usar SqlParameterSource para especificar valores de parámetros

Usar Map para especificar valores de parámetros Funciona bastante bien, pero no es la clase más fácil de usar. Spring contiene varias implementaciones de la interfaz SqlParameterSource que se pueden usar en su lugar. La primera es BeanPropertySqlParameterSource, que es una clase muy útil si tiene una clase compatible con JavaBean que contenga sus valores. Utiliza el captador apropiado para recuperar los valores de los parámetros. El siguiente ejemplo muestra cómo utilizar BeanPropertySqlParameterSource:

Java
public class JdbcActorDao implements ActorDao {
    private SimpleJdbcInsert insertActor;
    public void setDataSource(DataSource dataSource) {
        this.insertActor = new SimpleJdbcInsert(dataSource)
                .withTableName("t_actor")
                .usingGeneratedKeyColumns("id");
    }
    public void add(Actor actor) {
        SqlParameterSource parameters = new BeanPropertySqlParameterSource(actor);
        Number newId = insertActor.executeAndReturnKey(parameters);
        actor.setId(newId.longValue());
    }
    // ... métodos adicionales
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {
    private val insertActor = SimpleJdbcInsert(dataSource)
            .withTableName("t_actor")
            .usingGeneratedKeyColumns("id")
    fun add(actor: Actor): Actor {
        val parameters = BeanPropertySqlParameterSource(actor)
        val newId = insertActor.executeAndReturnKey(parameters)
        return actor.copy(id = newId.toLong())
    }
    // ... métodos adicionales
}

Otra opción es MapSqlParameterSource, que es similar a Map pero proporciona un método addValue más conveniente que se puede encadenar. El siguiente ejemplo muestra cómo usarlo:

Java
public class JdbcActorDao implements ActorDao {
    private SimpleJdbcInsert insertActor;
    public void setDataSource(DataSource dataSource) {
        this.insertActor = new SimpleJdbcInsert(dataSource)
                .withTableName("t_actor")
                .usingGeneratedKeyColumns("id");
    }
    public void add(Actor actor) {
        SqlParameterSource parameters = new MapSqlParameterSource()
                .addValue("first_name", actor.getFirstName())
                .addValue("last_name", actor.getLastName());
        Number newId = insertActor.executeAndReturnKey(parameters);
        actor.setId(newId.longValue());
    }
    // ... métodos adicionales
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {
    private val insertActor = SimpleJdbcInsert(dataSource)
            .withTableName("t_actor")
            .usingGeneratedKeyColumns("id")
    fun add(actor: Actor): Actor {
        val parameters = MapSqlParameterSource()
                    .addValue("first_name", actor.firstName)
                    .addValue("last_name", actor.lastName)
        val newId = insertActor.executeAndReturnKey(parameters)
        return actor.copy(id = newId.toLong())
    }
    // ... métodos adicionales
}

Como puede ver, la configuración es la misma. Para utilizar estas clases de entrada alternativas, solo necesita cambiar el código de ejecución.

Llamar a un procedimiento almacenado con SimpleJdbcCall

El La clase SimpleJdbcCall utiliza metadatos en la base de datos para buscar los nombres de los parámetros in y out, por lo que no es necesario declararlos explícitamente. Puede declarar parámetros si prefiere hacerlo o si tiene parámetros (como ARRAY o STRUCT) que no se asignan automáticamente a la clase Java. El primer ejemplo muestra un procedimiento simple que devuelve solo valores escalares en el formato VARCHAR y DATE de una base de datos MySQL. El procedimiento de ejemplo lee el registro de actor especificado y devuelve las columnas first_name, last_name y birth_date como parámetros out. El siguiente listado muestra el último ejemplo:

CREATE PROCEDURE read_actor (
    IN in_id INTEGER,
    OUT out_first_name VARCHAR(100),
    OUT out_last_name VARCHAR(100),
    OUT out_birth_date DATE)
BEGIN
    SELECT first_name, last_name, birth_date
    INTO out_first_name, out_last_name, out_birth_date
    FROM t_actor where id = in_id;
END;

El parámetro in_id contiene el id del actor que está buscando. Los parámetros out devuelven datos leídos de la tabla.

Puede declarar SimpleJdbcCall de manera similar a SimpleJdbcInsert. Debe crear una instancia y configurar la clase en el método de inicialización de su capa de acceso a datos. En comparación con la clase StoredProcedure, no es necesario crear subclases y declarar parámetros que se pueden buscar en los metadatos de la base de datos. El siguiente ejemplo de configuración SimpleJdbcCall utiliza el procedimiento almacenado anterior (el único parámetro de configuración distinto de DataSource es el nombre del procedimiento almacenado):

Java
public class JdbcActorDao implements ActorDao {
    private SimpleJdbcCall procReadActor;
    public void setDataSource(DataSource dataSource) {
        this.procReadActor = new SimpleJdbcCall(dataSource)
                .withProcedureName("read_actor");
    }
    public Actor readActor(Long id) {
        SqlParameterSource in = new MapSqlParameterSource()
                .addValue("in_id", id);
        Map out = procReadActor.execute(in);
        Actor actor = new Actor();
        actor.setId(id);
        actor.setFirstName((String) out.get("out_first_name"));
        actor.setLastName((String) out.get("out_last_name"));
        actor.setBirthDate((Date) out.get("out_birth_date"));
        return actor;
    }
    // ... métodos adicionales
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {
    private val procReadActor = SimpleJdbcCall(dataSource)
            .withProcedureName("read_actor")
    fun readActor(id: Long): Actor {
        val source = MapSqlParameterSource().addValue("in_id", id)
        val output = procReadActor.execute(source)
        return Actor(
                id,
                output["out_first_name"] as String,
                output["out_last_name"] as String,
                output["out_birth_date"] as Date)
    }
        // ... métodos adicionales
}

El código que escribe para realizar la llamada implica la creación de un SqlParameterSource que contiene el parámetro IN. Debe hacer coincidir el nombre especificado para el valor de entrada con el nombre del parámetro declarado en el procedimiento almacenado. No es necesario que coincidan las mayúsculas y minúsculas porque se utilizan metadatos para determinar cómo se debe hacer referencia a los objetos de la base de datos en el procedimiento almacenado. Lo que se especifica en el código fuente de un procedimiento almacenado no es necesariamente lo mismo que se almacena en la base de datos. Algunas bases de datos convierten los nombres a mayúsculas, mientras que otras usan minúsculas o usan un caso específico.

El método execute toma parámetros IN y devuelve un Map que contiene cualquier parámetro out, siendo los clave los nombres especificados en el procedimiento almacenado. En este caso, estos son out_first_name, out_last_name y out_birth_date.

La última parte de la execute El método crea una instancia Actor que se utilizará para devolver los datos recibidos. Nuevamente, es importante utilizar los nombres de los parámetros out tal como están declarados en el procedimiento almacenado. Además, el caso de los nombres de los parámetros out almacenados en el mapa resultante es el mismo que el caso de los nombres de los parámetros out en la base de datos, que pueden diferir entre bases de datos. Para hacer que el código sea más independiente de la plataforma, puede realizar búsquedas que no distingan entre mayúsculas y minúsculas o decirle a Spring que use LinkedCaseInSENSENTIMap. Para lograr esto último, puede crear su propio JdbcTemplate y establecer la propiedad setResultsMapCaseInSENSITIVE en true. Luego puede pasar esta instancia JdbcTemplate personalizada al constructor de su SimpleJdbcCall. El siguiente ejemplo muestra esta configuración:

Java
public class JdbcActorDao implements ActorDao {
    private SimpleJdbcCall procReadActor;
    public void setDataSource(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.setResultsMapCaseInsensitive(true);
        this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
                .withProcedureName("read_actor");
    }
    // ... métodos adicionales
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {
    private var procReadActor = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
        isResultsMapCaseInsensitive = true
    }).withProcedureName("read_actor")
    // ... métodos adicionales
}

Al hacer esto, evitará conflictos en el caso utilizado para los nombres de sus parámetros return.

Declarar explícitamente parámetros para su uso en SimpleJdbcCall

Anteriormente en este capítulo describimos cómo se infieren los parámetros a partir de los metadatos, pero puedes declararlos explícitamente si lo deseas. Puede hacer esto creando y configurando un SimpleJdbcCall usando el método declareParameters, que toma un número variable de objetos SqlParameter como entrada. Para obtener más información sobre cómo definir SqlParameter, consulte la siguiente sección.

La declaración explícita es necesaria si la base de datos que está utilizando no es una base de datos compatible con Spring. Actualmente, Spring admite la búsqueda de metadatos en llamadas a procedimientos almacenados para las siguientes bases de datos: Apache Derby, DB2, MySQL, Microsoft SQL Server, Oracle y Sybase. También admitimos la búsqueda de metadatos en funciones almacenadas para MySQL, Microsoft SQL Server y Oracle.

Puede declarar explícitamente uno, más o todos los parámetros. Los metadatos de parámetros todavía se utilizan cuando los parámetros no se declaran explícitamente. Para omitir todo el procesamiento de búsqueda de metadatos para parámetros potenciales y usar solo los parámetros declarados, puede llamar al método withoutProcedureColumnMetaDataAccess como parte de la declaración. Supongamos que se declaran dos o más firmas de llamada diferentes para una función de base de datos. En este caso, se llama a useInParameterNames para especificar una lista de nombres de parámetros IN que se incluirán en una firma determinada.

El siguiente ejemplo muestra una llamada a procedimiento completamente declarada y utiliza información del ejemplo anterior:

Java
public class JdbcActorDao implements ActorDao {
    private SimpleJdbcCall procReadActor;
    public void setDataSource(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.setResultsMapCaseInsensitive(true);
        this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
                .withProcedureName("read_actor")
                .withoutProcedureColumnMetaDataAccess()
                .useInParameterNames("in_id")
                .declareParameters(
                        new SqlParameter("in_id", Types.NUMERIC),
                        new SqlOutParameter("out_first_name", Types.VARCHAR),
                        new SqlOutParameter("out_last_name", Types.VARCHAR),
                        new SqlOutParameter("out_birth_date", Types.DATE)
                );
    }
    // ... métodos adicionales
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {
        private val procReadActor = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
            isResultsMapCaseInsensitive = true
        }).withProcedureName("read_actor")
                .withoutProcedureColumnMetaDataAccess()
                .useInParameterNames("in_id")
                .declareParameters(
                        SqlParameter("in_id", Types.NUMERIC),
                        SqlOutParameter("out_first_name", Types.VARCHAR),
                        SqlOutParameter("out_last_name", Types.VARCHAR),
                        SqlOutParameter("out_birth_date", Types.DATE)
    )
        // ... métodos adicionales
}

La ejecución y los resultados finales son los mismos en estos dos ejemplos. En el segundo ejemplo, todos los detalles se especifican explícitamente, en lugar de utilizar metadatos.

Formas de definir SqlParameters

Para defina un parámetro para las clases SimpleJdbc, así como para las clases de operación RDBMS (discutidas en "Modelado Operaciones JDBC como objetos Java ") puede utilizar SqlParameter o una de sus subclases. Para hacer esto, generalmente especifica el nombre del parámetro y el tipo SQL en el constructor. El tipo SQL se especifica utilizando las constantes java.sql.Types. Anteriormente en este capítulo, vimos declaraciones similares a las siguientes:

Java
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
Kotlin
SqlParameter("in_id", Types.NUMERIC),
SqlOutParameter("out_first_name", Types.VARCHAR),

La primera línea con SqlParameter declara el parámetro IN. Puede usar parámetros IN tanto para llamadas a procedimientos almacenados como para consultas usando SqlQuery y sus subclases (que se analizan en la sección "Introducción a SqlQuery").

La segunda línea (con SqlOutParameter) declara out parámetro a utilizar en la llamada al procedimiento almacenado. También hay un SqlInOutParameter para los parámetros InOut (parámetros que proporcionan un procedimiento con un valor IN y que también devuelven un valor).

Solo los parámetros declarados como SqlParameter y SqlInOutParameter se utilizan para proporcionar valores de entrada. Esto contrasta con la clase StoredProcedure, que (por razones de compatibilidad con versiones anteriores) permite proporcionar valores de entrada para los parámetros declarados como SqlOutParameter.

Para los parámetros IN, además del nombre y el tipo de SQL, puede especificar un conjunto para datos numéricos o un nombre de tipo para tipos de bases de datos creados de forma personalizada. Para los parámetros out, puede especificar un RowMapper para manejar la visualización de las filas devueltas por el cursor REF. Otra opción es especificar un SqlReturnType, que le permite definir un manejo personalizado de los valores de retorno.

Llamar a una función almacenada con SimpleJdbcCall

Llamar Puede definir una función almacenada de la misma manera que puede definir un procedimiento almacenado, excepto que especifica el nombre de la función en lugar del nombre del procedimiento. El método withFunctionName se utiliza como parte de la configuración para indicar que es necesario realizar una llamada a la función y luego se genera la cadena adecuada para llamar a la función. Para ejecutar una función, utiliza una llamada (executeFunction) especializada que devuelve el valor de retorno de la función como un objeto del tipo especificado, lo que significa que no necesita obtener el valor de retorno del mapa resultante. Un método auxiliar similar (llamado executeObject) también está disponible para procedimientos almacenados que tienen solo un parámetro out. El siguiente ejemplo (para MySQL) se basa en la función almacenada get_actor_name, que devuelve el nombre completo del actor:

CREATE FUNCTION get_actor_name (in_id INTEGER)
RETURNS VARCHAR(200) READS SQL DATA
BEGIN
    DECLARE out_name VARCHAR(200);
    SELECT concat(first_name, ' ', last_name)
        INTO out_name
        FROM t_actor where id = in_id;
    RETURN out_name;
END;

Para llamar a esta función, volvemos a crear un SimpleJdbcCall en el método de inicialización, como se muestra en el siguiente ejemplo:

Java
public class JdbcActorDao implements ActorDao {
    private SimpleJdbcCall funcGetActorName;
    public void setDataSource(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.setResultsMapCaseInsensitive(true);
        this.funcGetActorName = new SimpleJdbcCall(jdbcTemplate)
                .withFunctionName("get_actor_name");
    }
    public String getActorName(Long id) {
        SqlParameterSource in = new MapSqlParameterSource()
                .addValue("in_id", id);
        String name = funcGetActorName.executeFunction(String.class, in);
        return name;
    }
    // ... métodos adicionales
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {
    private val jdbcTemplate = JdbcTemplate(dataSource).apply {
        isResultsMapCaseInsensitive = true
    }
    private val funcGetActorName = SimpleJdbcCall(jdbcTemplate)
            .withFunctionName("get_actor_name")
    fun getActorName(id: Long): String {
        val source = MapSqlParameterSource().addValue("in_id", id)
        return funcGetActorName.executeFunction(String::class.java, source)
    }
    // ... métodos adicionales
}

Método utilizado executeFunction devuelve una String que contiene el valor de retorno de la llamada a la función.

Devuelve un ResultSet o un cursor REF desde un Objeto SimpleJdbcCall

Llamar a un procedimiento almacenado o una función que devuelve un conjunto de resultados es una tarea un poco confusa. Algunas bases de datos devuelven conjuntos de resultados durante el procesamiento de resultados JDBC, mientras que otras requieren un parámetro out registrado explícitamente de un tipo específico. Ambos enfoques requieren procesamiento adicional para iterar sobre el conjunto de resultados y procesar las filas devueltas. Con SimpleJdbcCall puede utilizar el método returningResultSet y declarar una implementación RowMapper que se utilizará para un parámetro determinado. Si se devuelve un conjunto de resultados mientras se procesan los resultados, entonces los nombres no se definirán, por lo que los resultados devueltos deben coincidir con el orden en que se declaran las implementaciones de RowMapper. El nombre especificado todavía se usa para almacenar la lista procesada de resultados en el mapa resultante que se devuelve desde la instrucción execute.

El siguiente ejemplo (para MySQL) utiliza un procedimiento almacenado que no acepta ningún parámetro IN y devuelve todas las filas de la tabla t_actor:

CREATE PROCEDURE read_all_actors()
BEGIN
 SELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a;
END;

Para llamar a este procedimiento, puede declarar un RowMapper. Dado que la clase que desea asignar sigue las reglas de JavaBean, puede usar BeanPropertyRowMapper, que se crea pasando la clase requerida para asignar al método newInstance. El siguiente ejemplo muestra cómo hacer esto:

Java
public class JdbcActorDao implements ActorDao {
    private SimpleJdbcCall procReadAllActors;
    public void setDataSource(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.setResultsMapCaseInsensitive(true);
        this.procReadAllActors = new SimpleJdbcCall(jdbcTemplate)
                .withProcedureName("read_all_actors")
                .returningResultSet("actors",
                BeanPropertyRowMapper.newInstance(Actor.class));
    }
    public List getActorsList() {
        Map m = procReadAllActors.execute(new HashMap<String, Object>(0));
        return (List) m.get("actors");
    }
    // ... métodos adicionales
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {
        private val procReadAllActors = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
            isResultsMapCaseInsensitive = true
        }).withProcedureName("read_all_actors")
                .returningResultSet("actors",
                        BeanPropertyRowMapper.newInstance(Actor::class.java))
    fun getActorsList(): List<Actor> {
        val m = procReadAllActors.execute(mapOf<String, Any>())
        return m["actors"] as List<Actor>
    }
    // ... métodos adicionales
}

Llamar a execute pasa un Map vacío porque esto la llamada no toma ningún parámetro. La lista de actores luego se recupera del mapa resultante y se devuelve al código de llamada.