原创

Spring系列第42篇:JdbcTemplate实现增删改查如此简单?

本来这篇文章要写spring事务的,但是事务中大部分案例会用到JdbcTemplate相关的功能,所以先把JdbcTemplate拿出来说一下。

什么是JdbcTemplate?

大家来回顾一下,java中操作db最原始的方式就是纯jdbc了,是不是每次操作db都需要加载数据库驱动、获取连接、获取PreparedStatement、执行sql、关闭PreparedStatement、关闭连接等等,操作还是比较繁琐的,spring中提供了一个模块,对jdbc操作进行了封装,使其更简单,就是本文要讲的JdbcTemplate,JdbcTemplate是Spring对JDBC的封装,目的是使JDBC更加易于使用。

下面我们来看一下JdbcTemplate到底怎么玩的?

JdbcTemplate使用步骤

  1. 创建数据源DataSource

  2. 创建JdbcTemplate,new JdbcTemplate(dataSource)

  3. 调用JdbcTemplate的方法操作db,如增删改查

public class DataSourceUtils {
    public static DataSource getDataSource() {
        org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("root123");
        dataSource.setInitialSize(5);
        return dataSource;
    }
}

@Test
public void test0() {
    //1.创建数据源DataSource
    DataSource dataSource = DataSourceUtils.getDataSource();
    //2.创建JdbcTemplate,new JdbcTemplate(dataSource)
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    //3.调用JdbcTemplate的方法操作db,如增删改查
    List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from t_user");
    System.out.println(maps);
}

输出

[{id=114, name=路人}, {id=115, name=java高并发}, {id=116, name=spring系列}]

t_user表数据

mysql> select id,name from t_user;
+-----+---------------+
| id  | name          |
+-----+---------------+
| 114 | 路人          |
| 115 | java高并发    |
| 116 | spring系列    |
+-----+---------------+
3 rows in set (0.00 sec)

上面查询返回了t_user表所有的记录,返回了一个集合,集合中是一个Map,Map表示一行记录,key为列名,value为列对应的值。

有没有感觉到特别的方便,只需要jdbcTemplate.queryForList("select * from t_user")这么简单的一行代码,数据就被获取到了。

下面我们继续探索更强大更好用的功能。

增加、删除、修改操作

JdbcTemplate中以update开头的方法,用来执行增、删、改操作,下面来看几个常用的。

无参情况

Api

int update(final String sql)

案例

@Test
public void test1() {
    DataSource dataSource = DataSourceUtils.getDataSource();
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    int updateRows = jdbcTemplate.update("INSERT INTO t_user (name) VALUE ('maven系列')");
    System.out.println("影响行数:" + updateRows);
}

有参情况1

Api

int update(String sql, Object... args)

案例

sql中使用?作为占位符。

@Test
public void test2() {
    DataSource dataSource = DataSourceUtils.getDataSource();
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    int updateRows = jdbcTemplate.update("INSERT INTO t_user (name) VALUE (?)", "mybatis系列");
    System.out.println("影响行数:" + updateRows);
}

有参情况2

Api

int update(String sql, PreparedStatementSetter pss)

通过PreparedStatementSetter来设置参数,是个函数式接口,内部有个setValues方法会传递一个PreparedStatement参数,我们可以通这个参数手动的设置参数的值。

案例

@Test
public void test3() {
    DataSource dataSource = DataSourceUtils.getDataSource();
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    int updateRows = jdbcTemplate.update("INSERT INTO t_user (name) VALUE (?)", new PreparedStatementSetter() {
        @Override
        public void setValues(PreparedStatement ps) throws SQLException {
            ps.setString(1, "mysql系列");
        }
    });
    System.out.println("影响行数:" + updateRows);
}

获取自增列的值

Api

public int update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder)

案例

@Test
public void test4() {
    DataSource dataSource = DataSourceUtils.getDataSource();
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    String sql = "INSERT INTO t_user (name) VALUE (?)";
    KeyHolder keyHolder = new GeneratedKeyHolder();
    int rowCount = jdbcTemplate.update(new PreparedStatementCreator() {
        @Override
        public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
            //手动创建PreparedStatement,注意第二个参数:Statement.RETURN_GENERATED_KEYS
            PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            ps.setString(1, "获取自增列的值");
            return ps;
        }
    }, keyHolder);
    System.out.println("新记录id:" + keyHolder.getKey().intValue());
}

输出

新记录id122
mysql> select id,name from t_user;
+-----+-----------------------+
| id  | name                  |
+-----+-----------------------+
| 114 | 路人                  |
| 115 | java高并发            |
| 116 | spring系列            |
| 117 | maven系列             |
| 118 | mysql系列             |
| 122 | 获取自增列的值        |
+-----+-----------------------+
6 rows in set (0.00 sec)

批量增删改操作

Api

int[] batchUpdate(final String[] sql);
int[] batchUpdate(String sql, List<Object[]> batchArgs);
int[] batchUpdate(String sql, List<Object[]> batchArgs, int[] argTypes);

案例

@Test
public void test5() {
    DataSource dataSource = DataSourceUtils.getDataSource();
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    List<Object[]> list = Arrays.asList(
            new Object[]{"刘德华"}, 
            new Object[]{"郭富城"}, 
            new Object[]{"张学友"}, 
            new Object[]{"黎明"});
    int[] updateRows = jdbcTemplate.batchUpdate("INSERT INTO t_user (name) VALUE (?)", list);
    for (int updateRow : updateRows) {
        System.out.println(updateRow);
    }
}

查询操作

查询一列单行

Api

/**
 * sql:执行的sql,如果有参数,参数占位符?
 * requiredType:返回的一列数据对应的java类型,如String
 * args:?占位符对应的参数列表
 **/
<T> T queryForObject(String sql, Class<T> requiredType, @Nullable Object... args)

案例

@Test
public void test6() {
    DataSource dataSource = DataSourceUtils.getDataSource();
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    String name = jdbcTemplate.queryForObject("select name from t_user where id = ?", String.class, 114);
    System.out.println(name);
}

输出

路人

db中对应数据

mysql> select name from t_user where id = 114;
+--------+
| name   |
+--------+
| 路人   |
+--------+
1 row in set (0.00 sec)

使用注意

若queryForObject中sql查询无结果时,会报错

如id为0的记录不存在

mysql> select name from t_user where id = 0;
Empty set (0.00 sec)
@Test
public void test7() {
    DataSource dataSource = DataSourceUtils.getDataSource();
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    String name = jdbcTemplate.queryForObject("select name from t_user where id = ?", String.class, 0);
    System.out.println(name);
}

运行,会弹出一个异常EmptyResultDataAccessException,期望返回一条记录,但实际上却没有找到记录,和期望结果不符,所以报错了

org.springframework.dao.EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

    at org.springframework.dao.support.DataAccessUtils.nullableSingleResult(DataAccessUtils.java:97)
    at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:784)

这种如何解决呢,需要用到查询多行的方式来解决了,即下面要说到的queryForList相关的方法,无结果的时候会返回一个空的List,我们可以在这个空的List上面做文章。

查询一列多行

Api

以queryForList开头的方法。

<T> List<T> queryForList(String sql, Class<T> elementType);
<T> List<T> queryForList(String sql, Class<T> elementType, @Nullable Object... args);
<T> List<T> queryForList(String sql, Object[] args, Class<T> elementType);
<T> List<T> queryForList(String sql, Object[] args, int[] argTypes, Class<T> elementType);

注意:

上面这个T虽然是泛型,但是只支持Integer.class String.class 这种单数据类型的,自己定义的Bean不支持。(所以用来查询单列数据)

elementType:查询结果需要转换为哪种类型?如String、Integer、Double。

案例

@Test
public void test8() {
    DataSource dataSource = DataSourceUtils.getDataSource();
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    //<T> List<T> queryForList(String sql, Class<T> elementType);
    List<String> list1 = jdbcTemplate.queryForList("select name from t_user where id>131", String.class);
    System.out.println("list1:" + list1);

    //<T> List<T> queryForList(String sql, Class<T> elementType, @Nullable Object... args);
    List<String> list2 = jdbcTemplate.queryForList("select name from t_user where id>?", String.class, 131);
    System.out.println("list2:" + list2);

    //<T> List<T> queryForList(String sql, Object[] args, Class<T> elementType);
    List<String> list3 = jdbcTemplate.queryForList("select name from t_user where id>?", new Object[]{131}, String.class);
    System.out.println("list3:" + list3);

    //<T> List<T> queryForList(String sql, Object[] args, int[] argTypes, Class<T> elementType);
    List<String> list4 = jdbcTemplate.queryForList("select name from t_user where id>?", new Object[]{131}, new int[]{java.sql.Types.INTEGER}, String.class);
    System.out.println("list4:" + list4);
}

输出

list1:[郭富城, 张学友, 黎明]
list2:[郭富城, 张学友, 黎明]
list3:[郭富城, 张学友, 黎明]
list4:[郭富城, 张学友, 黎明]

sql结果:

mysql> select name from t_user where id>131;
+-----------+
| name      |
+-----------+
| 郭富城    |
| 张学友    |
| 黎明      |
+-----------+
3 rows in set (0.00 sec)

查询单行记录,将记录转换成一个对象

Api

<T> T queryForObject(String sql, RowMapper<T> rowMapper);
<T> T queryForObject(String sql, Object[] args, RowMapper<T> rowMapper);
<T> T queryForObject(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper);
<T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args);

上面这些方法的参数中都有一个rowMapper参数,行映射器,可以将当前行的结果映射为一个自定义的对象。

@FunctionalInterface
public interface RowMapper<T> {

    /**
     * @param ResultSet 结果集
     * @param 当前结果集的第几行
     * @return 当前行的结果对象,将当前行的结果映射为一个自定义的对象返回
     */
    @Nullable
    T mapRow(ResultSet rs, int rowNum) throws SQLException;

}

JdbcTemplate内部会遍历ResultSet,然后循环调用RowMapper#mapRow,得到当前行的结果,将其丢到List中返回,如下:

List<T> results = new ArrayList<>();
int rowNum = 0;
while (rs.next()) {
    results.add(this.rowMapper.mapRow(rs, rowNum++));
}
return results;

案例

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {
    private Integer id;
    private String name;
}
@Test
public void test9() {
    DataSource dataSource = DataSourceUtils.getDataSource();
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    String sql = "select id,name from t_user where id = ?";
    //查询id为34的用户信息
    User user = jdbcTemplate.queryForObject(sql, new RowMapper<User>() {
        @Nullable
        @Override
        public User mapRow(ResultSet rs, int rowNum) throws SQLException {
            User user = new User();
            user.setId(rs.getInt(1));
            user.setName(rs.getString(1));
            return user;
        }
    }, 134);
    System.out.println(user);
}

输出

User(id=134, name=134)

使用注意

当queryForObject中sql查询无结果的时候,会报错,必须要返回一行记录

查询单行记录,返回指定的javabean

RowMapper 有个实现了类 BeanPropertyRowMapper,可以将结果映射为javabean。

@Test
public void test10() {
    DataSource dataSource = DataSourceUtils.getDataSource();
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    String sql = "select id,name from t_user where id = ?";
    //查询id为34的用户信息
    RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
    User user = jdbcTemplate.queryForObject(sql, rowMapper, 134);
    System.out.println(user);
}

查询多列多行,每行结果为一个Map

Api

List<Map<String, Object>> queryForList(String sql);
List<Map<String, Object>> queryForList(String sql, Object... args);

每行结果为一个Map,key为列名小写,value为列对应的值。

案例

@Test
public void test11() {
    DataSource dataSource = DataSourceUtils.getDataSource();
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    String sql = "select id,name from t_user where id>?";
    List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql, 130);
    System.out.println(maps);
}

输出

[{id=131, name=刘德华}, {id=132, name=郭富城}, {id=133, name=张学友}, {id=134, name=黎明}]

查询多列多行,将结果映射为javabean

Api

<T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object... args)

案例

@Test
public void test12() {
    DataSource dataSource = DataSourceUtils.getDataSource();
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    String sql = "select id,name from t_user where id>?";
    List<User> maps = jdbcTemplate.query(sql, new RowMapper<User>() {
        @Nullable
        @Override
        public User mapRow(ResultSet rs, int rowNum) throws SQLException {
            User user = new User();
            user.setId(rs.getInt(1));
            user.setName(rs.getString(1));
            return user;
        }
    }, 130);
    System.out.println(maps);
}

运行输出

[User(id=131, name=刘德华), User(id=132, name=郭富城), User(id=133, name=张学友), User(id=134, name=黎明)]

更简单的方式,使用BeanPropertyRowMapper

@Test
public void test13() {
    DataSource dataSource = DataSourceUtils.getDataSource();
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    String sql = "select id,name from t_user where id>?";
    List<User> maps = jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class), 130);
    System.out.println(maps);
}

输出

[User(id=131, name=刘德华), User(id=132, name=郭富城), User(id=133, name=张学友), User(id=134, name=黎明)]

总结

  1. 使用注意:JdbcTemplate中的getObject开头的方法,要求sql必须返回一条记录,否则会报错
  2. BeanPropertyRowMapper可以将行记录映射为javabean
  3. JdbcTemplate采用模板的方式操作jdbc变的特别的容易,代码特别的简洁,不过其内部没有动态sql的功能,即通过参数,动态生成指定的sql,mybatis在动态sql方面做的比较好,大家用的时候可以根据需求进行选择。

案例源码

git地址:
https://gitee.com/javacode2018/spring-series

本文案例对应源码:
spring-series\lesson-003-jdbctemplate\src\main\java\com\javacode2018\jdbctemplate\demo1\Demo1Test.java

路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。

Spring系列

  1. Spring系列第1篇:为何要学spring?
  2. Spring系列第2篇:控制反转(IoC)与依赖注入(DI)
  3. Spring系列第3篇:Spring容器基本使用及原理
  4. Spring系列第4篇:xml中bean定义详解(-)
  5. Spring系列第5篇:创建bean实例这些方式你们都知道?
  6. Spring系列第6篇:玩转bean scope,避免跳坑里!
  7. Spring系列第7篇:依赖注入之手动注入
  8. Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
  9. Spring系列第9篇:depend-on到底是干什么的?
  10. Spring系列第10篇:primary可以解决什么问题?
  11. Spring系列第11篇:bean中的autowire-candidate又是干什么的?
  12. Spring系列第12篇:lazy-init:bean延迟初始化
  13. Spring系列第13篇:使用继承简化bean配置(abstract & parent)
  14. Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
  15. Spring系列第15篇:代理详解(Java动态代理&cglib代理)?
  16. Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
  17. Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
  18. Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
  19. Spring系列第18篇:@import详解(bean批量注册)
  20. Spring系列第20篇:@Conditional通过条件来控制bean的注册
  21. Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
  22. Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
  23. Spring系列第23篇:Bean生命周期详解
  24. Spring系列第24篇:父子容器详解
  25. Spring系列第25篇:@Value【用法、数据来源、动态刷新】
  26. Spring系列第26篇:国际化详解
  27. Spring系列第27篇:spring事件机制详解
  28. Spring系列第28篇:Bean循环依赖详解
  29. Spring系列第29篇:BeanFactory扩展(BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor)
  30. Spring系列第30篇:jdk动态代理和cglib代理
  31. Spring系列第31篇:aop概念详解
  32. Spring系列第32篇:AOP核心源码、原理详解
  33. Spring系列第33篇:ProxyFactoryBean创建AOP代理
  34. Spring系列第34篇:@Aspect中@Pointcut 12种用法
  35. Spring系列第35篇:@Aspect中5中通知详解
  36. Spring系列第36篇:@EnableAspectJAutoProxy、@Aspect中通知顺序详解
  37. Spring系列第37篇:@EnableAsync & @Async 实现方法异步调用
  38. Spring系列第38篇:@Scheduled & @EnableScheduling定时器详解
  39. Spring系列第39篇:强大的Spel表达式
  40. Spring系列第40篇:缓存使用(@EnableCaching、@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig)
  41. Spring系列第41篇:@EnableCaching集成redis缓存

更多好文章

  1. Java高并发系列(共34篇)
  2. MySql高手系列(共27篇)
  3. Maven高手系列(共10篇)
  4. Mybatis系列(共12篇)
  5. 聊聊db和缓存一致性常见的实现方式
  6. 接口幂等性这么重要,它是什么?怎么实现?
  7. 泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!

正文到此结束
本文目录