Spring JDBC Template: simplificando el uso de SQL

Última actualización: 03/08/2022
logo spring

Generalmente, el uso y explotación de bases de datos relacionales en Java lo realizamos a través de herramientas ORM tales como Hibernate o Apache OpenJPA -algunas implementan el estándar JPA-. Permiten desarrollar la capa de persistencia de nuestras aplicaciones de forma muy rápida, con poco código y una alta abstracción sobre el modelo relacional y la (inevitable) API JDBC con que interactuamos en Java con las bases de datos. Eso sí, es necesario conocer estas herramientas muy bien para evitar graves problemas de rendimiento.

Spring no solo es compatible con estos productos, sino que su integración y configuración es automática cuando usamos Spring Boot. Asimismo, cuenta con Spring Data JPA, su propia capa de abstracción que nos facilita aún más el trabajo, tal y como explico en esta serie de tutoriales.

Sin embargo, en algunos escenarios no es una buena opción o incluso es inviable el uso de Hibernate y similares, al menos como única solución:

  • Bases de datos no normalizadas ya existentes, cuya estructura no podemos modificar, y que resultan muy complejas de modelar con entidades. Incluso tablas carentes de clave primaria.
  • Necesitamos SQL para aprovechar las funcionalidades proporcionadas por la propia de base datos y/o optimizar al máximo el rendimiento.
  • Capas de persistencia que se basan en el empleo intensivo de procedimientos almacenados y SQL, hasta tal punto que no obtenemos ningún beneficio en el uso de JPA.

Aunque JPA permite trabajar con SQL nativo y procedimientos almacenados (e Hibernate ofrece funcionalidades adicionales), tema del tutorial SQL nativo con JPA e Hibernate, en estos escenarios puede ser más práctico decantarnos por usar directamente JDBC.

Uso estándar de JDBC

Antes de pasar a «jugar» con Spring JDBC Template, echemos un vistazo rápido a la manera en la que se suele programar con JDBC. El siguiente método obtiene un listado de objetos a partir desde una tabla.

public List<Country> findAllPureJdbc() throws SQLException {
    try (Connection connection = jdbcTemplate.getDataSource().getConnection();
            PreparedStatement ps = connection.prepareStatement("SELECT * FROM countries");) {
          ResultSet resultSet = ps.executeQuery();
          return mapResults(resultSet);
    } 
}

private List<Country> mapResults(ResultSet resultSet) throws SQLException {
    List<Country> results = new ArrayList<>();
    while (resultSet.next()) {
        Country country = mapResult(resultSet);
        results.add(country);
    }
    return results;
}

private Country mapToCountry(ResultSet resultSet) throws SQLException {
    return new Country(
        resultSet.getLong("id"),
        resultSet.getString("name"),
        resultSet.getInt("population"));
}

Las acciones que se realizan son las siguientes:

  • Apertura de una conexión a la base de datos. En este caso se realiza de forma un tanto particular porque el código proviene de la aplicación de ejemplo que cuenta con Spring JDBC Template.
  • Creación de la consulta a ejecutar.
  • Proporcionar los parámetros de la consulta (en este ejemplo no hay).
  • Obtener el ResultSet con los resultados e iterar sobre ellos para
  • Asegurar el cierre de los recursos y gestionar las excepciones de tipo SQLException (siempre deben ser capturadas o lanzadas de forma explícita). Esta tarea la he simplificado usando un bloque try-with-resources.

Demasiado código para hacer una operación tan rutinaria, y ni siquiera hemos tenido que lidiar con transacciones. En la práctica, el único código que nos interesa escribir es la consulta y la transformación de los datos del ResultSet. El resto de líneas son código «boilerplate» que se va repitiendo una y otra vez de forma idéntica (o casi) en las llamadas a la base de datos.

Spring JDBC Template nos permitirá centrarnos en el código que importa, encargándose de lo demás y con la tranquilidad de saber que esas operaciones funcionan (podemos equivocarnos si las escribimos nosotros mismos). No tendremos que abrir conexiones, devolverlas, o gestionar las transacciones (podemos darnos el gustazo de usar la anotación @Transactional).

Proyecto de ejemplo

Exploraremos las capacidades básicas de Spring JDBC Template con un proyecto de ejemplo que accederá a una base de datos MySQL. Contendrá una única tabla, llamada countries, un procedimiento almacenado y una función.

Para mayor comodidad, he incluido un fichero Dockerfile en la raíz del proyecto con un script SQL que ya se encarga de todo. La imagen se puede construir y arrancar como un contenedor con estos comandos.

docker build -t spring-jdbctemplate-demo-mysql .
docker run -d -p 3306:3306 spring-jdbctemplate-demo-mysql

Docker 1: introducción

Docker 2: imágenes y contenedores

Sin bien usar Spring Boot es la mejor forma de configurar y gestionar los proyectos Spring, el de ejemplo no lo hace. Esto se explica por el hecho de que fue creado originalmente hace varios años y me parece útil conservarlo para ofrecer ejemplos modernos de Spring sin Spring Boot porque son difíciles de encontrar.

Aun así, doy las instrucciones básicas por si estás interesado en configurar un proyecto con Spring Boot y Jdbc Template.

Spring Boot

En Spring Boot, lo que haremos es añadir al pom el starter spring-boot-starter-jdbc, a menos que ya tengamos uno que lo incluya como spring-boot-starter-data-jpa. Además, necesitamos el controlador JDBC de nuestra base de datos.

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
 </dependency>

Configuramos la fuente de datos en el application.properties (o application.yml).

spring.datasource.url=jdbc:mysql://localhost:3306/country?serverTimezone=UTC
spring.datasource.username=demo
spring.datasource.password=demo

En el caso específico del proyecto de ejemplo, escribiremos pruebas con JUnit 4 que requieren del contexto de Spring. Por ello, se precisan estos dos starters. Para más información, consultar este tutorial.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
 </dependency> 

<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.hamcrest</groupId>
                <artifactId>hamcrest-core</artifactId>
             </exclusion>
        </exclusions>
 </dependency>
Configuración manual

Las clases que necesitamos se encuentran en el módulo spring-jdbc. Es dependencia de spring-orm, por lo que si ya lo tenemos en nuestra aplicación (por ejemplo, estamos usando Hibernate, JPA o Spring Data JPA) no hay que hacer nada.

<dependency>
    <groupId>org.springframework</groupId>
	<artifactId>spring-jdbc</artifactId>
    <version>${spring.version}</version>
</dependency>

Lo divertido empieza ahora, porque tenemos que configurar a mano la fuente de datos (interfaz DataSource), entre otros elementos. Vayamos con una configuración básica.

Necesitaremos el controlador JDBC de la base de datos, en nuestro caso MySQL. El DataSource en sí lo construiremos con el pool de conexiones HikariCP, el mismo que configura Spring Boot de forma predeterminada. Adicionalmente, utilizaremos log4j2 y el código lo probaremos con tests basados en JUnit 4. Estas son todas las dependencias.

	<dependencies>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>com.zaxxer</groupId>
			<artifactId>HikariCP</artifactId>
			<version>${hikari.version}</version>
		</dependency>

		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-core</artifactId>
			<version>${log4jframework.version}</version>
		</dependency>

		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-jcl</artifactId>
			<version>${log4jframework.version}</version>
		</dependency>

		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-slf4j-impl</artifactId>
			<version>${log4jframework.version}</version>
		</dependency>
		
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>${mysql.driver.version}</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring.version}</version>
			<scope>test</scope>
		</dependency>

	</dependencies>

Los parámetros de configuración de la base de datos están en el fichero /src/main/resources/db.properties.

jdbc.driverClassName = com.mysql.cj.jdbc.Driver
jdbc.username=demo
jdbc.password=demo
jdbc.url = jdbc:mysql://localhost:3306/country-test?serverTimezone=UTC

Ahora creamos el DataSource en un clase de configuración (@Configuration). Lo usaremos para crear en Spring el bean JdbcTemplate con el que vamos a trabajar, así como el gestor de transacciones.

@Configuration
@PropertySource("classpath:db.properties")
@ComponentScan("com.danielme.spring")
public class AppConfiguration {

    @Bean(destroyMethod = "close")
    DataSource dataSource(Environment env) {
        HikariConfig config = new HikariConfig();
        config.setDriverClassName(env.getRequiredProperty("jdbc.driverClassName"));
        config.setJdbcUrl(env.getRequiredProperty("jdbc.url"));
        config.setUsername(env.getRequiredProperty("jdbc.username"));
        config.setPassword(env.getRequiredProperty("jdbc.password"));
        return new HikariDataSource(config);
    }

    @Bean
    JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

}
Las clases de ejemplo

Con independencia de cómo se configuró el proyecto, JdbcTemplate lo inyectamos como cualquier bean donde queremos, generalmente en una clase de tipo DAO. En el proyecto tendremos la siguiente.

@Repository
public class CountryDao {

    private final JdbcTemplate jdbcTemplate;

    public CountryDao(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

}

Los métodos que iremos creando se probarán con tests escritos en la clase CountryDaoTest, siguiendo lo explicado en Testing Spring con JUnit 4.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {ApplicationContext.class})
@Sql("/reset.sql")
public class CountryDaoTest {

    @Autowired
    private CountryDao countryDao;

}

Si tuviéramos Spring Boot, reemplazaremos la línea 2 por la anotación @SpringBootTest.

La anotación @Sql ejecuta el script /src/test/resources/reset.sql antes que las pruebas contenidas en la clase para establecer en la base de datos el juego de datos esperado.

BEGIN;
TRUNCATE countries;
INSERT INTO `countries` (`id`,`name`, `population`) VALUES (1, 'Mexico',  130497248);
INSERT INTO `countries` (`id`, `name`, `population`) VALUES (2, 'Spain', 49067981);
INSERT INTO `countries` (`id`,`name`, `population`) VALUES (3, 'Colombia', 46070146);
COMMIT;

Como es habitual, las pruebas se lanzan con el comando mvn test o bien desde un IDE como Eclipse o IntelliJ.

La imagen tiene un atributo ALT vacío; su nombre de archivo es spring-junit.png

Operaciones de consulta

La forma general de realizar una consulta JDBCTemplate que devuelva una lista de objetos consiste en proporcionar a un método query una cadena con el SQL y una implementación de RowMapper que convierta la información del ResultSet en un listado de objetos. Si no hay resultados, la lista será vacía, nunca nula.

    public List<Country> findAll() {
        return jdbcTemplate.query("SELECT * FROM countries", new CountryRowMapper());
    }

    private static class CountryRowMapper implements RowMapper<Country> {
        @Override
        public Country mapRow(ResultSet rs, int rowNum) throws SQLException {
            return new Country(rs.getLong("id"), rs.getString("name"), rs.getInt("population"));
        }
    }

Naturalmente, se pueden usar lambdas.

 public List<Country> findAll() {
    return jdbcTemplate.query("SELECT * FROM countries", (rs, rowNum) -> mapToCountry(rs));
 }

private Country mapToCountry(ResultSet rs) throws SQLException {
        return new Country(
                rs.getLong("id"),
                rs.getString("name"),
                rs.getInt("population"));
 }


Si fuera necesario, proporcionamos como tercer argumento un array (varags) con los parámetros a reemplazar en la consulta.

    public List<Country> findByName(String name) {
        return jdbcTemplate.query("SELECT * FROM countries WHERE name LIKE ?",
                (rs, rowNum) -> mapToCountry(rs), name);
    }


Para las consultas que pueden devolver como máximo un registro contamos con las sobrecargas de los métodos queryForObject.

public Optional<Country> findById(Long id) {
    Country country = jdbcTemplate.queryForObject("SELECT * FROM countries WHERE id = ?",
            (rs, rowNum) -> mapToCountry(rs), id);
    return Optional.ofNullable(country);
}

public int count() {
    return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM countries", Integer.class);
}

El método count es especialmente interesante: si la consulta devuelve un tipo primitivo, lo podemos retornar tal cual.

Operaciones de modificación

Las operaciones de modificación se ejecutan con los métodos update. Nos informan del número de registros afectados por la operación.

public int deleteAll() {
    return jdbcTemplate.update("DELETE from countries");
}

public void insertWithQuery(String name, int population) {
    jdbcTemplate.update("INSERT INTO countries (name, population) VALUES(?,?)", name, population);
}

Las operaciones de inserción más sencillas pueden definirse sin necesidad de escribir el INSERT en SQL, configurando en su lugar un objeto de la clase SimpleJdbcInsert. Pero el gran beneficio que aporta es el retorno del identificador asignado al nuevo registro.

    public long insertWithSimpleJdbcInsert(String name, int population) {
        SimpleJdbcInsert simpleJdbcInsert =
                new SimpleJdbcInsert(jdbcTemplate.getDataSource())
                        .withTableName("countries")
                        .usingGeneratedKeyColumns("id");

        Map<String, Object> parameters = new HashMap<>();
        parameters.put("name", name);
        parameters.put("population", population);
        return simpleJdbcInsert.executeAndReturnKey(parameters).longValue();
    }

Esta prueba demuestra que, en efecto, insertWithSimpleJdbcInsert retorna la clave primaria.

@Test
public void testInsert() {
    long idReturned = countryDao.insertWithSimpleJdbcInsert(TEST_NAME, 123456);
    long id = countryDao.findByName(TEST_NAME).get(0).getId();
    assertEquals(idReturned, id);
}

Modificaciones masivas en batch

La agrupación de operaciones de modificación en lotes (batch processing) mejora el rendimiento. Es notablemente más rápida la ejecución de n inserciones en una única sentencia INSERT que en n sentencias. Recordemos que INSERT admite este formato.

INSERT INTO table_name (columns)
VALUES
    (columns1),
    (columns2),
    ...
    (columns_n);

JdbcTemplate nos pone muy fácil aprovechar esta característica de SQL gracias a las sobrecargas del método batchUpdate. Hagamos una inserción masiva en la tabla countries definiendo además el tamaño de cada lote. Para ello necesitamos crear la sentencia SQL de inserción como un PreparedStatement estándar de JDBC y una implementación de la interfaz ParameterizedPreparedStatementSetter en la que rellenamos los parámetros de ese PreparedStatement.

    public void insertBatch(List<Country> countries, int batchSize) {
        String sql = "INSERT INTO countries (name, population) VALUES(?,?)";

        jdbcTemplate.batchUpdate(sql, countries, batchSize,
                (PreparedStatement ps, Country country) -> {
                    ps.setString(1, country.getName());
                    ps.setInt(2, country.getPopulation());
                }
        );
    }

Este test inserta 500 países en lotes de 100.

    @Test
    public void testBatchInsert() {
        List<Country> countries = IntStream.rangeClosed(1, 500)
                .boxed()
                .map(i -> new Country(String.valueOf(i), i))
                .collect(Collectors.toList());
        long init = System.currentTimeMillis();

        countryDao.insertBatch(countries, 100);

        assertEquals(countries.size() + 3, countryDao.count());
        System.out.println(System.currentTimeMillis() - init + " ms");
    }

En el caso concreto de MySQL, se requiere activar la opción rewriteBatchedStatements.

jdbc.url = jdbc:mysql://localhost:3306/demoTemplate?autoReconnect=true&useSSL=false&rewriteBatchedStatements=true

Este es un ejemplo de la traza de ejecución.

10-28-2018 06:30:05,313 PM DEBUG JdbcTemplate:1014 - Executing SQL batch update [INSERT INTO country (name, population) VALUES(?,?)] with a batch size of 100
10-28-2018 06:30:05,315 PM DEBUG JdbcTemplate:622 - Executing prepared SQL statement [INSERT INTO country (name, population) VALUES(?,?)]
10-28-2018 06:30:05,315 PM DEBUG DataSourceUtils:110 - Fetching JDBC Connection from DataSource
10-28-2018 06:30:05,339 PM DEBUG JdbcUtils:408 - JDBC driver supports batch updates
10-28-2018 06:30:05,342 PM DEBUG JdbcTemplate:1036 - Sending SQL batch update #1 with 100 items
10-28-2018 06:30:05,348 PM DEBUG JdbcTemplate:1036 - Sending SQL batch update #2 with 100 items
10-28-2018 06:30:05,357 PM DEBUG JdbcTemplate:1036 - Sending SQL batch update #3 with 100 items
10-28-2018 06:30:05,365 PM DEBUG JdbcTemplate:1036 - Sending SQL batch update #4 with 100 items
10-28-2018 06:30:05,374 PM DEBUG JdbcTemplate:1036 - Sending SQL batch update #5 with 100 items
10-28-2018 06:30:05,381 PM DEBUG DataSourceUtils:329 - Returning JDBC Connection to DataSource
10-28-2018 06:30:05,383 PM DEBUG JdbcTemplate:453 - Executing SQL query [SELECT COUNT(*) FROM country]
10-28-2018 06:30:05,384 PM DEBUG DataSourceUtils:110 - Fetching JDBC Connection from DataSource
10-28-2018 06:30:05,394 PM DEBUG DataSourceUtils:329 - Returning JDBC Connection to DataSource
152 ms

Con rewriteBatchedStatements activado, la prueba apenas me tarda 200-300ms. Desactivado, nunca baja de 3 segundos.

NamedParameterJdbcTemplate

Existe otra forma de utilizar Jdbc Template. NamedParameterJdbcTemplate es un wrapper (envoltorio) de JdbcTemplate que ofrece versiones de algunos métodos que admiten el nombrado de los parámetros o variables de las sentencias SQL, en vez de emplear el comodín ‘?’. Una característica que puede que te resulte familiar, pues está disponible el lenguaje de consultas JPQL de JPA.

Para usar NamedParameterJdbcTemplate, procedemos a inyectarla.

@Repository
public class CountryDao {

    private final JdbcTemplate jdbcTemplate;
    private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    public CountryDao(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
        this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
    }

Si no usamos Spring Boot, hay que crear su instancia y registrarla en el contenedor de beans de Spring del mismo modo que hicimos con JdbcTemplate.

 @Bean
 NamedParameterJdbcTemplate namedParameterJdbcTemplate(DataSource dataSource) {
    return new NamedParameterJdbcTemplate(dataSource);
 }

Este es un ejemplo sencillo. No parece que NamedParameterJdbcTemplate mejore sustancialmente nuestro código, pero cuando el SQL tiene muchos parámetros convierte a la consulta más legible.

    public List<Country> findByPopulation(int minPopulation, int maxPopulation) {
        Map<String, Integer> params = new HashMap<>();
        params.put("minPopulation", minPopulation);
        params.put("maxPopulation", maxPopulation);
        return namedParameterJdbcTemplate.query("SELECT * FROM countries WHERE population " +
                        "BETWEEN :minPopulation AND :maxPopulation ORDER BY name",
                params,
                (rs, rowNum) -> mapToCountry(rs));
    }

Podemos acceder a la instancia de JdbcTemplate contenida en NamedParameterJdbcTemplate para invocar a sus métodos.

namedParameterJdbcTemplate.getJdbcTemplate();

Procedimientos y funciones

Spring JDBC Template también nos asiste a la hora de ejecutar procedimientos y funciones SQL. El siguiente procedimiento posee un parámetro de entrada y otro de salida.

DELIMITER //
CREATE DEFINER=`root`@`localhost` PROCEDURE `search`(IN name VARCHAR(50), OUT total INT)
BEGIN
    SELECT COUNT(*) INTO total FROM countries c WHERE c.name LIKE name;
END//
DELIMITER ;

Para ejecutarlo construimos una instancia de SimpleJdbcCall indicando el nombre del procedimiento. Esa instancia ejecutará el procedimiento tras la invocación al método execute que recibe los parámetros de entrada y devuelve los de salida en un Map.

    public Integer callProcedure(String name) {
        SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(jdbcTemplate)
                .withProcedureName("search");
        SqlParameterSource in = new MapSqlParameterSource().addValue("name", name);
        Map<String, Object> out = simpleJdbcCall.execute(in);
        return (Integer) out.get("total");
    }

La imagen de Docker ya está preparada para que esta prueba funcione.

    @Test
    public void testProcedure() {
        assertEquals(0, (int) countryDao.callProcedure(FUNC_PROC_NAME));
    }

Si usas tu propia base de datos tendrás que crear el procedimiento. Además, es necesario que el usuario con el que Spring realice la conexión pueda leer los metadatos de los procedimientos almacenados. En caso contrario, verás el siguiente error.

org.springframework.dao.TransientDataAccessResourceException: CallableStatementCallback; SQL [{call search()}]; User does not have access to metadata required to determine stored procedure parameter types

Podemos asignar los permisos al usuario, «user» en el ejemplo, con esta sentencia.

GRANT SELECT ON mysql.proc TO 'user'@'localhost'

Si algún parámetro de salida fuera un ResultSet, Spring puede procesarlo con un RowMapper. Supongamos que el procedimiento anterior tiene un parámetro de salida, denominado «countries», que es un ResultSet.

public List<Country> callProcedure(String name) {
		SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(jdbcTemplate).withProcedureName("search")
				.returningResultSet("countries", new CountryRowMapper());

		SqlParameterSource in = new MapSqlParameterSource().addValue("name", name);

		Map<String, Object> out = simpleJdbcCall.execute(in);
		return (List<Country>) out.get("countries");
	}

Ahora, veamos cómo invocar una función. Las funciones devuelven un valor simple (cadena, número, etcétera). Los parámetros, cuando los hay, son siempre de entrada (IN).

DELIMITER //
    CREATE FUNCTION `search2`(name VARCHAR(50)) RETURNS INT
        BEGIN DECLARE total INT;
        SELECT COUNT(*) INTO total
        FROM countries c
        WHERE c.name LIKE name;

        RETURN total;
    END//
DELIMITER ;

El código Java es análogo al anterior, pero recurriendo los métodos withFunctionName y executeFunction en lugar de withProcedureName y execute.

    public Integer callFunction(String name) {
        SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(jdbcTemplate)
                .withFunctionName("search2");
        SqlParameterSource in = new MapSqlParameterSource().addValue("name", name);
        return simpleJdbcCall.executeFunction(Integer.class, in);
    }

Código de ejemplo

El proyecto completo se encuentra disponible en GitHub. Para más información sobre cómo utilizar GitHub, consultar este artículo.

Otros tutoriales relacionados

SQL nativo con JPA e Hibernate

Persistencia en BD con Spring: Integrando JPA, c3p0, Hibernate y EHCache

Persistencia en BD con Spring Data JPA

Testing Spring con JUnit 4

2 comentarios sobre “Spring JDBC Template: simplificando el uso de SQL

  1. Desde el momento que le metiste C3P0, log4j, Junit y varias cosas mas para hacer el ejemplo. Dejo de ser simple

  2. El insert con SimpleJdbcInsert debería devolverme el id del registro guardado, pero en vez de eso, siempre me da este error:
    PreparedStatementCallback; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: ORA-00928: falta la palabra clave SELECT\n

    Como mas puedo obtenerlo ?

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.